Skip to content
Docs/SDK/Rust
Edit on GitHub

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:

Bash
cargo add opensecstack
cargo add tokio --features full

Or edit Cargo.toml directly:

Cargo.toml
YAML
[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.

Bash
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.

Bash
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:

Bash
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).

Bash
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).

Bash
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.

Bash
[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 exp is parsed from the token payload; the SDK refreshes tokens 60 seconds before expiry to prevent mid-flight failures (SDK-M5).
  • TLS: Uses rustls exclusively — native-tls is 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:

https://github.com/opensecstack/opensecstack/tree/main/sdk

SIN — Security Intelligence Network — Open-source cybersecurity & compliance for the EU Digital Decade.

Apache-2.0 & AGPL-3.0 · GitHub · Contribute