Skip to content

v0.68.1 β€” Security Patch

Choose a tag to compare

@github-actions github-actions released this 04 Apr 18:18

πŸ¦‹ 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 env

The 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