v0.68.1 β Security Patch
π¦ Nika 0.68.1 β Security Patch
Inference as Code Β· April 4, 2026 Β· 14 commits
| π§ͺ Tests | π§ Builtins | π¦ Transforms | π Providers |
|---|---|---|---|
| ~9,800 | 61 | 52 | 7 |
β§ infer Β· β exec Β· β fetch Β· β invoke Β· β agent
β¨ This Release in 30 Seconds
Pure security. Zero feature changes. Six audit findings across three severity levels, all resolved in one patch. The headline: Nika now has zero unsafe code in the entire codebase β the last remaining unsafe { std::env::set_var() } call was replaced with a thread-safe in-memory SecretStore. YAML parsing is now budget-capped against billion-laughs attacks, SSRF redirect chains are re-validated per hop, and secrets are cryptographically zeroized on drop.
π C-1 Β· Zero Unsafe β Thread-Safe SecretStore
Severity: π΄ Critical Β· The most important fix in this release.
Rust's std::env::set_var() is undefined behavior in multi-threaded programs (as of Rust 1.66). Nika was using it to inject API keys into the process environment before provider calls β a holdover from early single-threaded days. In the multi-threaded Tokio runtime that Nika uses for parallel task execution, this was a ticking time bomb.
Before:
// UB in multi-threaded context β data race on environ
unsafe { std::env::set_var("ANTHROPIC_API_KEY", key) }After:
// Thread-safe, zero unsafe, zero UB
let store = SecretStore::new(); // Arc<RwLock<HashMap<String, String>>>
store.set("ANTHROPIC_API_KEY", key);
// Provider reads from store, never from envThe new SecretStore is an Arc<RwLock<HashMap>> that replaces all environment variable mutation for secrets. Providers read from the store, never from std::env. The result: zero unsafe blocks in the entire Nika codebase β 18 crates, ~150K lines of Rust, all safe.
Tip
You can verify this yourself: cargo geiger now reports zero unsafe usage across the workspace.
π H-1 Β· YAML Billion-Laughs Defense
Severity: π High
The billion laughs attack (a.k.a. XML bomb / YAML bomb) exploits recursive entity expansion to consume exponential memory from a small input. A malicious workflow file could craft nested YAML anchors that expand into gigabytes of data, crashing the engine.
Before: serde-saphyr (Nika's YAML parser) had no expansion budget β a 1 KB file could expand to 1 GB.
After: Budget limits are enforced on every YAML parsing path in the engine β workflow files, nika check, nika serve input validation, config loading. Entity expansion is capped and recursion depth is limited. A billion-laughs payload now fails fast with a clear error instead of eating all your RAM.
π H-2 Β· SSRF-Safe Redirect Chains
Severity: π High
Nika's fetch: verb validates URLs against SSRF (Server-Side Request Forgery) before making requests β blocking private IP ranges like 127.0.0.1, 10.x.x.x, and 169.254.x.x. But HTTP redirects weren't re-validated. An attacker could use an open redirect on a public server to bounce Nika into an internal network:
https://public.example.com/redirect β http://169.254.169.254/latest/meta-data/
Before: Only the initial URL was checked. Redirects followed blindly.
After: Every 3xx hop is re-validated against the SSRF blocklist. A redirect from public.com β 169.254.169.254 is now blocked at the redirect, not just at the initial request. This applies to both the fetch: verb runtime and the TUI chat's link-following.
π M-6 Β· ListSecrets Auth Gate
Severity: π‘ Medium
The Nika daemon exposes an IPC endpoint for listing stored secret names (not values β just which providers have keys configured). This endpoint was unauthenticated β any local process could enumerate which API keys were stored.
After: The ListSecrets request now requires the daemon auth token, matching the security posture of all other daemon endpoints. Unauthenticated enumeration is closed.
π M-7 Β· Zeroize Secrets on Drop
Severity: π‘ Medium
When Nika decrypts vault secrets for provider authentication, the plaintext briefly lives in memory. After use, the VaultPayload struct was dropped β but Rust's default drop doesn't overwrite memory, it just marks it as free. A memory dump or core dump could reveal cleartext API keys.
After: VaultPayload and all intermediate plaintext buffers now derive Zeroize from the zeroize crate. On drop, memory is actively overwritten with zeros before being freed. Secrets never linger in freed memory.
π QA-1 Β· Async I/O Everywhere
Severity: π΅ Quality
Several file I/O operations in async code paths were using blocking std::fs calls instead of tokio::fs. In a high-concurrency scenario (like nika serve handling multiple simultaneous jobs), a blocking read could starve the Tokio runtime's thread pool, causing latency spikes for all other tasks.
After: All file operations in async contexts now use tokio::fs β non-blocking, cooperative with the runtime, no thread starvation.
π Security Summary Table
| ID | Severity | Finding | Fix |
|---|---|---|---|
| C-1 | π΄ Critical | unsafe { std::env::set_var() } in multi-threaded runtime |
Thread-safe SecretStore (Arc<RwLock<HashMap>>) β zero unsafe remaining |
| H-1 | π High | YAML parsing without expansion budget | serde-saphyr budget enforced on ALL parsing paths |
| H-2 | π High | SSRF bypass via open redirect chains | Redirect policy re-validates each 3xx hop against blocklist |
| M-6 | π‘ Medium | ListSecrets daemon endpoint unauthenticated |
Auth token required for secret enumeration |
| M-7 | π‘ Medium | Plaintext secrets persist in freed memory | Zeroize derive on VaultPayload + plaintext buffers |
| QA-1 | π΅ Quality | Blocking std::fs in async code paths |
Replaced with tokio::fs β no thread pool starvation |
Audit status: 6/6 resolved β
β¬οΈ Upgrade Notes
[!NOTE]
This is a drop-in replacement for v0.68.0. No configuration changes, no workflow syntax changes, no breaking API changes. Just upgrade and enjoy better security.
The SecretStore change (C-1) is internal to the engine β if you're using $env.API_KEY in workflows or environment variables for provider configuration, nothing changes from your perspective. The engine just reads them more safely now.
π¦ Install
| Method | Command | |
|---|---|---|
| π | Quick | curl -fsSL https://raw.githubusercontent.com/supernovae-st/nika/main/install.sh | sh |
| πΊ | Homebrew | brew install supernovae-st/tap/nika |
| π¦ | npm | npx @supernovae-st/nika |
| π¦ | Cargo | cargo install nika |
| π³ | Docker | docker run --rm ghcr.io/supernovae-st/nika:0.68.1 |
| π» | VS Code | ext install supernovae.nika-lang |
| πͺ | Scoop | scoop bucket add nika https://github.com/supernovae-st/scoop-nika && scoop install nika |
| π§ | AUR | yay -S nika-bin |
π All binaries include SHA256 checksums, SLSA provenance, and macOS notarization.
Made with π by SuperNovae Studio β Paris, Open Source, AGPL-3.0
Full Changelog: v0.68.0...v0.68.1