Rust SDK
The Rust client provides async, type-safe clients for APIGuard and NIS2 Compass built on tokio + reqwest, with serde for JSON, thiserror for structured errors, and a builder pattern for client configuration. It requires Rust 1.75+ (stable). See SDK & Contracts for the full integration contract reference.
Installation
Add the crate and the tokio async runtime to your Cargo.toml:
cargo add opensecstack cargo add tokio --features full
Or edit Cargo.toml directly:
[dependencies]
opensecstack = "0.1"
tokio = { version = "1", features = ["full"] }Client construction
Use the ::new constructor for sensible defaults (30 s timeout, 2 retries, rustls TLS), or the builder for custom settings.
use opensecstack::APIGuardClient;
use std::time::Duration;
// Default settings
let client = APIGuardClient::new(
"https://apiguard.internal",
"ak_live_...",
);
// Custom settings via builder
let client = APIGuardClient::builder("https://apiguard.internal", "ak_live_...")
.timeout(Duration::from_secs(60))
.max_retries(3)
.retry_wait_base(Duration::from_millis(250))
.build();Running a scan
Start a scan asynchronously, poll until it reaches a terminal status, then retrieve and print critical findings. All async functions must be .awaited inside a #[tokio::main] context.
use opensecstack::{APIGuardClient, CreateScanOptions, ScanStatus,
GetFindingsOptions, FindingSeverity};
use tokio::time::{sleep, Duration};
"cm">#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = APIGuardClient::new("https://apiguard.internal", "ak_live_...");
// Start scan
let scan = client.create_scan("https://api.example.com/openapi.json").await?;
println!("Scan ID: {}", scan.id);
// Poll until complete
let completed = loop {
sleep(Duration::from_secs(5)).await;
let s = client.get_scan(&scan.id).await?;
match s.status {
ScanStatus::Completed | ScanStatus::Failed | ScanStatus::Cancelled => break s,
_ => {}
}
};
println!("{} findings", completed.total_findings);
// Retrieve critical findings
let findings = client.get_findings(&scan.id, Some(GetFindingsOptions {
severity: Some(FindingSeverity::Critical),
..Default::default()
})).await?;
for f in &findings {
println!("[{}] {} {} — {}", f.owasp_id, f.endpoint_method, f.endpoint_path, f.title);
}
Ok(())
}Use create_scan_full to configure modules, target URL, and authentication:
use opensecstack::CreateScanOptions;
let scan = client.create_scan_full(CreateScanOptions {
target_url: "https://api.example.com".to_string(),
spec_url: Some("https://api.example.com/openapi.json".to_string()),
modules: vec!["bola".to_string(), "broken_auth".to_string(), "injection".to_string()],
auth_type: Some("bearer".to_string()),
auth_token: Some("eyJhb...".to_string()),
..Default::default()
}).await?;NIS2 Compass client
Manage organisations and NIS2 assessments. Controls follow NIS2 Article 21(2) and are identified by letters 'a' through 'j'. Audit log entries include a tamper-evident hash chain (prev_hash / chain_hash).
use opensecstack::{NIS2CompassClient, CreateOrganisationRequest, OrganisationSize,
CreateAssessmentRequest, PatchControlRequest, ControlStatus,
ArtifactType};
let client = NIS2CompassClient::new("https://nis2compass.internal", "nk_live_...");
// Create organisation
let org = client.create_organisation(CreateOrganisationRequest {
name: "Example GmbH".to_string(),
industry: Some("financial_services".to_string()),
country: Some("DE".to_string()),
size: Some(OrganisationSize::Medium),
registration_number: Some("HRB 123456 B".to_string()),
}).await?;
// Create assessment
let assessment = client.create_assessment(
&org.id.to_string(),
CreateAssessmentRequest {
title: "NIS2 Annual Assessment 2026".to_string(),
scope: Some("All production systems".to_string()),
assessor: Some("ciso@example.de".to_string()),
due_date: Some("2026-12-31".to_string()),
},
).await?;
// Mark control 'a' as compliant with evidence
let updated = client.patch_control(
&assessment.id.to_string(),
'a',
PatchControlRequest {
status: Some(ControlStatus::Compliant),
evidence: Some(serde_json::json!({
"document_ref": "IS-POL-001",
"approved_by": "CISO",
"approved_at": "2026-01-15"
})),
risk_score: Some(2.5),
..Default::default()
},
).await?;
// Upload evidence artifact tied to the control
let artifact = client.upload_artifact(
&assessment.id.to_string(),
"/path/to/security_policy.pdf",
ArtifactType::Policy,
Some(&updated.id.to_string()),
Some("Information Security Policy v3.0"),
).await?;
println!("Artifact: {} ({} bytes)", artifact.filename, artifact.size_bytes);Error handling
The crate exposes a single Error enum with variants for every failure mode. Match on it in your error-handling code to implement targeted recovery (for example, respecting retry_after on 429 responses).
use opensecstack::Error;
match result {
Err(Error::NotFound(path)) => { /* 404 */ }
Err(Error::RateLimit { retry_after }) => { /* 429 — back off */ }
Err(Error::Auth(msg)) => { /* bad API key */ }
Err(Error::Api { status, code, .. }) => { /* structured API error */ }
Err(Error::Transport(e)) => { /* network failure */ }
Err(Error::Json(e)) => { /* parse failure */ }
Err(Error::Io(e)) => { /* file I/O failure */ }
_ => {}
}CITADEL MARSHAL (Kerkese)
opensecstack::citadel::CITADELClient (above) is a WORM event-delivery client — send_event/get_events/verify_chain — it does not submit a Kerkese for MARSHAL evaluation. That's a separate crate, citadel-kerkese-core, published from the same sdk/rust/ directory: a no_std + alloc core for building, Ed25519-signing, and submitting CITADEL MARSHAL Kerkese requests, for hosts that can't pull in Tokio/reqwest at all (a freestanding kernel being the motivating case). It does no networking of its own — a KerkeseTransport trait is the host-supplied I/O boundary, so the same core works both from a freestanding kernel and from a hosted std process via a reqwest-backed transport.
[dependencies] citadel-kerkese-core = "1.0.0"
See sdk/rust/citadel-kerkese-core's own README and examples/ for the full API and a worked std transport example.
Security notes
- No redirect following: HTTP redirect following is disabled on all clients (SDK-M4) to prevent Bearer tokens from leaking to redirect targets.
- Proactive token refresh: JWT
expis parsed from the token payload; the SDK refreshes tokens 60 seconds before expiry to prevent mid-flight failures (SDK-M5). - TLS: Uses
rustlsexclusively —native-tlsis not a dependency. - No secrets in logs: The SDK never logs API keys or access tokens; tracing calls include only method, path, and status code at DEBUG level.
Full documentation
Other SDK language clients: Go SDK, Python SDK, TypeScript SDK.
The complete Rust client reference — including all type definitions, builder options, shared types, and runnable examples — is available in the SDK repository: