A complete, production-ready Rust implementation of STIX 2.1 (Structured Threat Information Expression)
stix-rs provides full support for creating, parsing, and manipulating STIX 2.1 cyber threat intelligence data. Built for performance and type safety, it's ready for use in threat intelligence platforms, TAXII servers, and security tools.
- ✅ All 18 STIX Domain Objects (SDOs) - Malware, Indicator, ThreatActor, Campaign, etc.
- ✅ All 17 Cyber Observable Objects (SCOs) - File, Network Traffic, Process, etc.
- ✅ All Relationship Objects (SROs) - Relationship, Sighting
- ✅ All Meta Objects - Bundle, Marking Definition, Language Content, etc.
- ✅ 17 Vocabulary Enums - Complete type-safe enumerations
- 🔍 Bundle Query Helpers - Powerful search and filter APIs
- ✅ ID Validation - Prevent invalid STIX references
- 📡 MIME Type Constants - Standard HTTP/TAXII content types
- 🔄 Object Versioning - Proper STIX object update handling
- 🎨 Pattern Validation - STIX pattern language syntax checking
- 📦 Builder Pattern - Ergonomic object construction
- Full Rust type safety with no runtime overhead
- Comprehensive error handling with
thiserror - Efficient serialization/deserialization with
serde - Zero-copy parsing where possible
Add to your Cargo.toml:
[dependencies]
stix-rs = "0.1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4"use stix_rs::*;
use chrono::Utc;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a Malware object
let malware = Malware::builder()
.name("Poison Ivy")
.description("Advanced persistent threat RAT")
.malware_types(vec!["remote-access-trojan".into()])
.is_family(true)
.aliases(vec!["PIVY".into()])
.first_seen(Utc::now())
.build()?;
// Create an Indicator
let indicator = Indicator::builder()
.name("Malicious domain")
.pattern("[domain-name:value = 'evil.com']")
.pattern_type(IndicatorPatternType::Stix)
.valid_from(Utc::now())
.validate_pattern(true) // Enable pattern validation
.build()?;
// Create a Bundle
let bundle = Bundle::new(vec![
malware.into(),
indicator.into(),
]);
// Serialize to JSON
let json = serde_json::to_string_pretty(&bundle)?;
println!("{}", json);
Ok(())
}use stix_rs::*;
// Load a bundle from JSON
let bundle: Bundle = serde_json::from_str(&json_data)?;
// Query by type
let all_malware = bundle.malware();
let all_indicators = bundle.indicators();
let all_threats = bundle.threat_actors();
// Find by ID
if let Some(obj) = bundle.get("malware--abc-123") {
println!("Found: {}", obj.type_());
}
// Filter generically
let identities = bundle.filter_by_type("identity");
// Count objects
println!("Total malware: {}", bundle.count_by_type("malware"));
println!("Object types: {:?}", bundle.object_types());
// Find references
let refs = bundle.find_references_to("malware--abc-123");
// Iterate
for obj in bundle.iter() {
println!("{}: {}", obj.type_(), obj.id());
}use stix_rs::*;
let mut malware = Malware::builder()
.name("BadWare")
.malware_types(vec!["trojan".into()])
.build()?;
// Add common properties
malware.common.labels = Some(vec!["apt".into(), "targeted".into()]);
malware.common.confidence = Some(95);
malware.common.lang = Some("en".into());
// Add external references (CVE, ATT&CK)
malware.common.external_references = Some(vec![
ExternalReference::builder()
.source_name("mitre-attack")
.external_id("S0020")
.url("https://attack.mitre.org/software/S0020/")
.build()?,
]);
// Add TLP marking
let tlp_red = MarkingDefinition::tlp("red");
malware.common.object_marking_refs = Some(vec![tlp_red.id().to_string()]);use stix_rs::pattern::{validate_pattern, PatternBuilder};
// Validate patterns
validate_pattern("[file:hashes.MD5 = 'abc123']")?;
validate_pattern("[ipv4-addr:value = '192.168.1.1']")?;
// Build patterns programmatically
let pattern = PatternBuilder::new()
.compare("file", "name", "=", "'malware.exe'")
.and()
.compare("file", "size", ">", "1000")
.build();
println!("{}", pattern);
// Output: [file:name = 'malware.exe' AND file:size > 1000]use stix_rs::*;
// Validate STIX IDs
assert!(is_valid_stix_id("malware--550e8400-e29b-41d4-a716-446655440000"));
assert!(!is_valid_stix_id("invalid-id"));
// Extract type from ID
let obj_type = extract_type_from_id("malware--abc-123");
assert_eq!(obj_type, Some("malware"));
// Validate reference types
assert!(is_valid_ref_for_type(
"malware--abc-123",
"malware"
));use stix_rs::*;
let mut threat_actor = ThreatActor::builder()
.name("APT28"<
8000
/span>)
.threat_actor_types(vec!["nation-state".into()])
.build()?;
// Make updates
threat_actor.description = Some("Also known as Fancy Bear".into());
// Create new version (updates modified timestamp, preserves ID)
threat_actor.common.new_version();use stix_rs::*;
// Use standard STIX/TAXII MIME types
println!("{}", MEDIA_TYPE_STIX); // application/stix+json;version=2.1
println!("{}", MEDIA_TYPE_TAXII); // application/taxii+json;version=2.1use axum::{Router, routing::get, Json, http::StatusCode};
use stix_rs::*;
async fn get_objects() -> (StatusCode, [(String, String); 1], Json<Bundle>) {
let bundle = load_threat_intel();
(
StatusCode::OK,
[("Content-Type".to_string(), MEDIA_TYPE_STIX.to_string())],
Json(bundle)
)
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/collections/1/objects/", get(get_objects));
// ... serve
}| Object | Builder | Tests | Fields |
|---|---|---|---|
| Attack Pattern | ✅ | ✅ | Complete |
| Campaign | ✅ | ✅ | Complete |
| Course of Action | ✅ | ✅ | Complete |
| Grouping | ✅ | ✅ | Complete |
| Identity | ✅ | ✅ | Complete |
| Incident | ✅ | ✅ | Complete |
| Indicator | ✅ | ✅ | Complete + Validation |
| Infrastructure | ✅ | ✅ | Complete |
| Intrusion Set | ✅ | ✅ | Complete |
| Location | ✅ | ✅ | Complete |
| Malware | ✅ | ✅ | Complete + Extended |
| Malware Analysis | ✅ | ✅ | Complete |
| Note | ✅ | ✅ | Complete |
| Observed Data | ✅ | ✅ | Complete |
| Opinion | ✅ | ✅ | Complete |
| Report | ✅ | ✅ | Complete |
| Threat Actor | ✅ | ✅ | Complete |
| Tool | ✅ | ✅ | Complete |
| Vulnerability | ✅ | ✅ | Complete |
| Object | Support |
|---|---|
| Artifact | ✅ |
| Autonomous System | ✅ |
| Directory | ✅ |
| Domain Name | ✅ |
| Email Address | ✅ |
| Email Message | ✅ |
| File | ✅ |
| IPv4 Address | ✅ |
| IPv6 Address | ✅ |
| MAC Address | ✅ |
| Mutex | ✅ |
| Network Traffic | ✅ |
| Process | ✅ |
| Software | ✅ |
| URL | ✅ |
| User Account | ✅ |
| Windows Registry Key | ✅ |
| X.509 Certificate | ✅ |
| Object | Support |
|---|---|
| Relationship | ✅ |
| Sighting | ✅ |
| Object | Support |
|---|---|
| Bundle | ✅ + Query Helpers |
| Marking Definition | ✅ + TLP Support |
| Language Content | ✅ |
| Extension Definition | ✅ |
| External Reference | ✅ + Builder |
| Granular Marking | ✅ |
All STIX 2.1 open vocabularies are implemented as type-safe enums:
MalwareType- ransomware, trojan, backdoor, etc. (20 types)ThreatActorType- nation-state, criminal, hacktivist, etc. (12 types)ThreatActorRole- director, agent, sponsor, etc. (5 roles)ThreatActorSophistication- minimal, intermediate, advanced, etc. (7 levels)AttackMotivation- ideology, dominance, personal-gain, etc. (10 types)AttackResourceLevel- individual, club, organization, government (6 levels)ToolType- exploitation, remote-access, etc. (8 types)InfrastructureType- command-and-control, botnet, etc. (11 types)ReportType- threat-report, attack-pattern, etc. (11 types)IndustrySector- financial, healthcare, government, etc. (40+ sectors)IndicatorType- malicious-activity, anomalous-activity, etc.ImplementationLanguage- Python, C++, JavaScript, etc. (20+ languages)IndicatorPatternType- stix, pcre, snort, yara, suricataIdentityClass- individual, group, organization, systemHashAlgorithm- MD5, SHA-1, SHA-256, SHA-512RelationshipType- targets, uses, indicates, etc.EncryptionAlgorithm- AES-256-GCM, ChaCha20-Poly1305
# Run all tests
cargo test
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test bundle_query
# Check for compilation issues
cargo check
# Build release
cargo build --releaseTest Coverage: 78 tests passing (57 unit + 21 integration/doc tests)
- Official STIX 2.1 Spec - OASIS specification
- STIX 2.1 Examples - Official examples
cargo doc --openQuery and analyze threat data with type-safe APIs:
let apt_malware: Vec<_> = bundle.malware()
.iter()
.filter(|m| m.name.contains("APT"))
.collect();Serve STIX bundles with proper MIME types:
response.header("Content-Type", MEDIA_TYPE_STIX);Parse and create STIX indicators programmatically:
let indicator = Indicator::builder()
.pattern(pattern)
.valid_from(Utc::now())
.build()?;Merge multiple STIX feeds efficiently:
let mut combined = Bundle::new(vec![]);
combined.objects.extend(feed1.objects);
combined.objects.extend(feed2.objects);Exchange standardized threat intelligence:
let bundle = Bundle::new(vec![
malware.into(),
threat_actor.into(),
relationship.into(),
]);let identity = Identity::builder()
.name("ACME Corp")
.class(IdentityClass::Organization)
.property("x_industry", "financial")
.property("x_priority", 5)
.build()?;let extension = ExtensionDefinition::builder()
.name("my-extension")
.version("1.0.0")
.schema("https://example.com/schema.json")
.extension_types(vec!["property-extension".into()])
.build()?;malware.common.granular_markings = Some(vec![
GranularMarking {
marking_ref: Some("marking-definition--tlp-red".into()),
selectors: vec!["name".into(), "description".into()],
lang: None,
}
]);# Clone the repository
git clone https://github.com/yourusername/stix-rs
cd stix-rs
# Run tests
cargo test
# Check formatting
cargo fmt --check
# Run clippy
cargo clippy
# Build docs
cargo doc --openThis project is licensed under the MIT License - see the LICENSE file for details.
- OASIS CTI Technical Committee - STIX specification