| Crates.io | policy-core |
| lib.rs | policy-core |
| version | 1.0.0 |
| created_at | 2025-12-29 02:59:39.024618+00 |
| updated_at | 2025-12-29 02:59:39.024618+00 |
| description | Compile-time policy enforcement and taint tracking framework for Rust |
| homepage | https://github.com/camadkins/policy-core |
| repository | https://github.com/camadkins/policy-core |
| max_upload_size | |
| id | 2009879 |
| size | 362,025 |
Compile-time policy enforcement and taint tracking for Rust.
policy-core prevents injection attacks, unauthorized access, and accidental data leaks using Rust's type system. Untrusted input is wrapped in a Tainted<T> type with no public accessors. To perform side effects—logging, database writes, HTTP requests—the data must pass through a Sanitizer that validates it and returns Verified<T>. Sinks accept only Verified<T>, making compile-time bypass structurally impossible.
Tainted<T> → Sanitizer → Verified<T> → Sink
Ctx<Unauthed> → Ctx<Authed> → Ctx<Authorized>)cargo add policy-core
Here's a minimal example demonstrating taint tracking:
use policy_core::{Tainted, Sanitizer, StringSanitizer, Sink, VecSink};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Step 1: Mark untrusted input as tainted
let user_input = Tainted::new(" hello world ".to_string());
// Step 2: Sanitize with validation rules
let sanitizer = StringSanitizer::new(256);
let verified = sanitizer.sanitize(user_input)?;
// Step 3: Pass verified data to sink
let sink = VecSink::new();
sink.sink(&verified)?;
// Verify the result (trimmed whitespace)
assert_eq!(sink.to_vec(), vec!["hello world"]);
println!("Success! Input was validated and processed safely.");
Ok(())
}
The type system prevents bypassing validation at compile time. Attempting to pass Tainted<T> directly to a sink results in a compile error. See the examples/ directory for complete demonstrations.
┌─────────────┐
│ Raw Input │ (user form, API call, file)
└──────┬──────┘
│
▼
┌─────────────────┐
│ Tainted<T> │ Mark as untrusted at boundary
└──────┬──────────┘
│
▼
┌─────────────────┐
│ Sanitizer │ Validate according to policy
└──────┬──────────┘
│
▼
┌─────────────────┐
│ Verified<T> │ Guaranteed safe by construction
└──────┬──────────┘
│
▼
┌─────────────────┐
│ Sink │ Perform side effect
└─────────────────┘
Key properties:
Tainted<T> to Verified<T> transition requires explicit validationpub(crate) constructors)Tainted<T>Marks data from untrusted sources (user input, network requests, files). The inner value is inaccessible:
Deref, AsRef, From, or Into implementationspub(crate) accessor existsSanitizerValidates and promotes tainted data to verified data:
pub trait Sanitizer<T> {
fn sanitize(&self, input: Tainted<T>) -> Result<Verified<T>, SanitizationError>;
}
Implementations define validation rules and call Verified::new_unchecked only after validation succeeds. Errors do not leak rejected input.
The crate includes StringSanitizer, which trims whitespace, rejects control characters, and enforces length limits.
Verified<T>Data that has passed validation:
pub(crate) fn new_uncheckedDeref, From, Into, or Defaultas_ref() and into_inner()Verified<T> except through a SanitizerThis creates a validation bottleneck: all paths from untrusted input to sinks must pass through explicit sanitization.
SinkOperations that perform side effects (writes, logs, network calls):
pub trait Sink<T> {
fn sink(&self, value: &Verified<T>) -> Result<(), SinkError>;
}
By accepting only &Verified<T>, sinks reject Tainted<T> at compile time.
The crate includes VecSink, an in-memory sink for testing.
These patterns demonstrate when and how to use policy-core's core abstractions. See the examples/ directory for complete working code.
When to use: Mark all external input—user forms, API requests, file contents—as tainted at system boundaries.
Key insight: Tainted<T> prevents accidental use of unvalidated data. The type system forces explicit validation before sinks accept the data.
Reference: examples/basic_taint_flow.rs, integration tests in tests/taint_tracking_test.rs
When to use: Create verified contexts that carry proof of authentication and authorization through your application.
Key insight: PolicyGate validates policies before constructing a Ctx. Operations requiring specific capabilities demand the corresponding Ctx state as a parameter.
Reference: examples/policy_gate_validation.rs
When to use: Gate access to sensitive operations (logging, database writes, HTTP calls) behind unforgeable capability tokens.
Key insight: Capabilities have pub(crate) constructors. External code cannot forge them—they must be granted through policy validation.
Reference: examples/audit_trail.rs
For complete demonstrations of these patterns integrated together, see src/demo.rs and the full integration test suite.
use policy_core::{Tainted, Sanitizer, StringSanitizer, Sink, VecSink};
// Step 1: Mark untrusted input as tainted
let user_input = Tainted::new(" hello world ".to_string());
// Step 2: Sanitize with validation rules
let sanitizer = StringSanitizer::new(256);
let verified = sanitizer
.sanitize(user_input)
.expect("valid input passes");
// Step 3: Pass verified data to sink
let sink = VecSink::new();
sink.sink(&verified).expect("sink succeeds");
// Verify the side effect
assert_eq!(sink.to_vec(), vec!["hello world"]); // trimmed
// This does NOT compile (type error):
// let tainted = Tainted::new("unsafe".to_string());
// sink.sink(&tainted); // Expected &Verified<String>, got &Tainted<String>
The sanitizer trims whitespace, rejects empty strings, blocks control characters, and enforces length limits. Invalid input produces a SanitizationError without leaking the rejected value.
The dylint/ directory contains custom Dylint lints that enforce architectural invariants at compile time.
Purpose: Prevent accidental bypass of capability gating, taint tracking, and explicit context patterns.
Run locally:
cargo install cargo-dylint dylint-link
cargo dylint --all --workspace
CI: Lints run automatically on every PR. Deny-level violations fail the build.
Documentation: See dylint/README.md for:
policy-core keeps dependencies minimal:
tracing — Structured logging for policy decisions, sanitizer results, and sink activity. Does not affect type-level guarantees.
tracing-subscriber — Test and demo support for log collection. Used to show how policy decisions surface in logs while keeping side effects in-memory.
Core types (Tainted<T>, Verified<T>, Sanitizer, Sink) depend only on the standard library. Logging is optional.
The project is configured with optimized build profiles for faster development iteration.
For even faster builds, create .cargo/config.toml in the project root (this file is git-ignored):
[build]
# Use lld linker (faster than default ld)
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
sudo apt install lld clang # Ubuntu/Debian
brew install llvm # macOS
# See: https://github.com/rui314/mold
# After installing, change rustflags to: ["-C", "link-arg=-fuse-ld=mold"]
cargo build - Fast incremental builds (debug = 1, 20-30% faster than default)cargo build --profile dev-opt - Slightly optimized for performance testingcargo build --release - Full optimizationcargo test - Fast test buildsThe development profile uses debug = 1 (line tables only) instead of debug = 2 (full debug info), reducing build times by 20-30% with minimal impact on debugging capability. Release builds use thin LTO for optimal performance.
policy-core provides compile-time guarantees that prevent entire classes of vulnerabilities. However, like all security tools, it works best as part of a comprehensive defense strategy.
Important Notes:
unsafe code, deserialization, and FFI boundaries.For threat model details and best practices, see SECURITY.md and DESIGN_PHILOSOPHY.md.
examples/ directorydylint/README.mdThis project follows Semantic Versioning. See crates.io/crates/policy-core for the latest stable version and CHANGELOG.md for release history.
API Stability: Starting with version 1.0.0, this crate follows strict Semantic Versioning (SemVer 2.0.0). Patch releases (1.0.x) contain bug fixes and documentation improvements only. Minor releases (1.x.0) add new features in a backwards-compatible manner. Major releases (2.0.0+) may include breaking changes to public APIs. All pub items are covered by stability guarantees.
Contributions are welcome! For significant changes, please open an issue first to discuss your approach.
Before submitting a pull request:
cargo fmt, cargo clippy --all-targets --all-features -- -D warnings, cargo test --all-features, cargo test --doc --all-features, cargo dylint --all --workspaceSee DESIGN_PHILOSOPHY.md for architectural principles.
This project is licensed under the MIT License.