v0.68.2 β SDK Hardening
π¦ Nika 0.68.2 β SDK Hardening
Inference as Code Β· April 4, 2026 Β· 24 commits
| π§ͺ Tests | π§ Builtins | π¦ Transforms | π Providers |
|---|---|---|---|
| ~9,800 | 62 | 52 | 7 |
β§ infer Β· β exec Β· β fetch Β· β invoke Β· β agent
β¨ This Release in 30 Seconds
The SDK gets its reckoning. 24 commits, 5 media security fixes, 8 SDK type fixes, a new builtin, and the official deprecation of nika-napi. The cancel() return type was wrong (CRITICAL), HTTP 429 and 503 status codes were inverted, and JobStatus::Pending simply didn't exist. Meanwhile, the media pipeline gains import path confinement, pipeline step limits, SVG validation, and PDF thread caps. The new nika:decode builtin bridges base64-returning providers (Gemini, fal.ai, Stability AI) into Nika's content-addressable storage. This is what Feature Freeze looks like β no new syntax, just relentless quality.
πΌοΈ nika:decode β Base64 to CAS in One Step
Some AI providers return generated images as base64 strings instead of URLs. Gemini's image generation, fal.ai's Stable Diffusion endpoints, Stability AI's API β they all hand you a fat base64 blob. Before this release, getting that into Nika's media pipeline required writing it to a temp file, then importing. Awkward.
nika:decode eliminates the middleman. Feed it a base64 string, get back a CAS hash ready for the full media pipeline β thumbnails, format conversion, optimization, everything.
tasks:
- id: generate
fetch:
url: "https://api.stability.ai/v1/generation/image"
method: POST
headers:
Authorization: "Bearer {{$env.STABILITY_API_KEY}}"
json:
prompt: "A butterfly made of starlight"
response: full
- id: store
with:
b64: $generate.body
invoke:
tool: nika:decode
params:
data: "{{with.b64}}"
media_type: "image/png"
- id: thumbnail
with:
hash: $store.hash
invoke:
tool: nika:thumbnail
params:
input: "{{with.hash}}"
width: 256Tip
nika:decode auto-detects the media type from the data when media_type is omitted, but explicit is always better for API responses where the Content-Type might not be embedded in the base64 payload.
This brings the builtin tool count to 62.
π Media Pipeline Security β 5 Hardening Fixes
The media pipeline handles untrusted input by nature β user-uploaded images, downloaded files, API responses. Five targeted fixes close the remaining attack surface.
π‘οΈ SEC-5 Β· Import Path Confinement
Severity: π High
nika:import could previously import files from anywhere on the filesystem. A workflow with nika:import and a path template like {{with.user_path}} could be tricked into importing /etc/passwd or ~/.nika/secrets/vault.enc.
After: All import paths are now confined to the project's working directory. Symlinks are resolved before validation. ../ traversal is blocked. A path outside the project root returns NIKA-291 (MediaSecurityError).
π‘οΈ SEC-6 Β· Pipeline Step Limit (50)
Severity: π‘ Medium
The nika:pipeline tool chains media operations in-memory. Without a limit, a malicious or buggy workflow could chain thousands of steps, exhausting memory. Now capped at 50 steps per pipeline invocation.
π‘οΈ SEC-7 Β· Pipeline Budget Enforcement
Severity: π‘ Medium
Pipeline operations track estimated memory cost. The budget was calculated but never actually enforced β the pipeline would warn but proceed. Now the budget is checked before each step, and the pipeline aborts if the budget is exceeded.
π‘οΈ SEC-8 Β· SVG Zero-Size Guard
Severity: π‘ Medium
nika:svg_render converts SVG to PNG via resvg. An SVG with viewBox="0 0 0 0" caused a division by zero in the rasterizer. Dimensions exceeding 10,000px in either direction could also cause excessive memory allocation.
After: Zero-size viewBoxes are rejected. Dimensions above 10,000px are rejected. Only valid, bounded SVGs proceed to rasterization.
π‘οΈ SEC-9 Β· PDF Thread Limit (4)
Severity: π‘ Medium
nika:pdf_extract spawns threads for parallel page extraction. In a for_each loop processing many PDFs simultaneously, thread count could explode. Now capped at 4 concurrent extraction threads per process.
π Security Summary Table
| ID | Severity | Vulnerability | Fix |
|---|---|---|---|
| SEC-5 | π High | nika:import could read outside project dir |
Path confined to working directory, symlinks resolved |
| SEC-6 | π‘ Medium | Unbounded pipeline step count | Hard cap at 50 steps per pipeline |
| SEC-7 | π‘ Medium | Pipeline budget calculated but not enforced | Budget check before each step, abort on overspend |
| SEC-8 | π‘ Medium | SVG div-by-zero + oversized allocation | Reject zero-size viewBox and dims > 10,000px |
| SEC-9 | π‘ Medium | Unbounded PDF extraction threads | Cap at 4 concurrent threads |
π§ SDK Type Safety Overhaul β 8 Fixes
The TypeScript SDK (@supernovae-st/nika-client) and Python SDK both got a thorough audit. What we found was... not great. Here's every fix, explained.
π΄ CRITICAL Β· cancel() Return Type
The cancel() method was typed to return StatusResponse β the same type as status(). But the server returns a CancelResponse with different fields. TypeScript would compile, then crash at runtime when accessing fields that didn't exist.
Before:
const result = await client.cancel(jobId);
console.log(result.status); // runtime error: status is not a field on CancelResponseAfter: cancel() correctly returns CancelResponse. Type-safe at compile time, correct at runtime.
π JobStatus::Pending β Missing Enum Variant
When nika serve queues a job (max_concurrent reached), it sends status: "pending" over SSE. The SDK's JobStatus enum didn't have a Pending variant β just Running, Completed, and Failed. Receiving a "pending" event caused a parse error, breaking the entire SSE stream.
After: JobStatus.Pending is now a first-class variant. Queued jobs are reported correctly.
π HTTP 429/503 β Inverted Status Mapping
This one's embarrassing. The SDK mapped HTTP 429 Too Many Requests to the ServiceUnavailable error class, and HTTP 503 Service Unavailable to RateLimited. The retry logic keyed off these classes β so rate-limited responses got no retry delay, and server overload got exponential backoff. Exactly backwards.
After: 429 β RateLimited (with Retry-After header support). 503 β ServiceUnavailable. A new RateLimited error variant makes the distinction explicit.
π‘ NIKA-XXX Error Code Parsing
When the server returns an error with a Nika error code (e.g., NIKA-045: Fetch timeout), the SDK was supposed to parse the NIKA-XXX code into a structured field. The regex was wrong β it silently dropped the code, leaving the error as an unparsed string.
After: Error codes are correctly extracted via regex and available on the error object as .nikaCode.
π‘ ArtifactInfo.format β Wrong Optionality
The TypeScript type declared format as string | undefined, but the server always sends the format field. Code that checked if (artifact.format) worked, but code that used artifact.format! (non-null assertion) was technically lying. More importantly, code that destructured { format = "text" } got the wrong default on every call.
After: format is string (required, non-optional). Matches the wire format.
π‘ Embedded Transport β 3 Fixes
The embedded transport (for running Nika as a library rather than a server) had three issues:
- π
list_artifactssilently returned an empty array on error instead of propagating the failure - βΈοΈ
resumewould hang indefinitely on a dead job instead of fast-failing - π΄
cancelsent the cancel signal but didn't abort the underlying process, leaving zombie workflows
All three are fixed. list_artifacts propagates errors. resume detects dead jobs and fails immediately. cancel properly aborts the process.
π‘ SSE Buffer β 1 MiB β 2 MiB
Large workflow outputs (especially for_each loops with many items) could exceed the 1 MiB SSE event buffer. When this happened, the event was silently truncated β the SDK received partial JSON that failed to parse, breaking the stream.
After: Buffer doubled to 2 MiB. Events that would exceed the buffer are now split across multiple SSE frames.
π‘ Python SDK β Thread Safety + Auth Mapping
Two Python-specific fixes:
- π§΅ Worker threads β The Python SDK spawns worker threads for SSE processing. Thread count was fixed at 1 regardless of workload. Now scales to CPU count with
os.cpu_count(). - π Unauthorized mapping β HTTP 401 was mapped to a generic
ApiErrorinstead of the specificAuthenticationError. Error handling code that caughtAuthenticationErrornever fired.
π§ SDK Fix Summary Table
| Layer | Fix | Severity | Impact |
|---|---|---|---|
| π¦ TypeScript | cancel() return type β CancelResponse |
π΄ Critical | Runtime crash on cancel |
| π¦ TypeScript | JobStatus.Pending variant added |
π High | SSE stream break on queued jobs |
| π¦ TypeScript | HTTP 429β503 mapping corrected | π High | Inverted retry behavior |
| π¦ TypeScript | NIKA-XXX code regex fixed | π‘ Medium | Error codes silently lost |
| π¦ TypeScript | ArtifactInfo.format β required string |
π‘ Medium | Wrong optionality |
| βοΈ Embedded | list_artifacts error propagation |
π‘ Medium | Silent empty array on error |
| βοΈ Embedded | resume fast-fail on dead jobs |
π‘ Medium | Infinite hang |
| βοΈ Embedded | cancel abort + SSE buffer 1β2 MiB |
π‘ Medium | Zombie process + truncated events |
| π Python | Worker thread scaling to CPU count | π‘ Medium | Single-threaded bottleneck |
| π Python | 401 β AuthenticationError mapping |
π‘ Medium | Wrong error class |
β οΈ Deprecated: nika-napi
The N-API bindings package (nika-napi) is officially deprecated. Use @supernovae-st/nika-client instead β it's a pure TypeScript SDK that talks to nika serve over HTTP/SSE, with full type safety, automatic retry, and streaming support.
nika-napi will be removed in v0.70.
Warning
If you're importing from nika-napi in a Node.js project, migrate to @supernovae-st/nika-client now. The API surface is similar but not identical β see the migration guide.
π Other Fixes
- π§Ή
nika servecancel endpoint now returnsStatusResponsefor SDK wire compatibility - π MCP schema updated to reflect 62 tools, 50 transforms,
nika:decode - π§ͺ 15 new
EmbeddedTransporttests (was 0) β roundtrip, error propagation, cancel abort - π§ͺ Cross-crate
ServeEvent β Eventwire format roundtrip test - π‘οΈ HTTP body truncated in error messages to prevent log flooding
- π‘οΈ Percent-encoded path traversal (
%2e%2e%2f) blocked in addition to raw../
β¬οΈ Upgrade Notes
[!WARNING]
TypeScript SDK users: If you're on@supernovae-st/nika-client< 0.68.2, upgrade now. Thecancel()return type change is a compile-time breaking change (in your favor β it was wrong before). TheJobStatus.Pendingvariant may require updating exhaustive switch statements.
[!NOTE]
Python SDK users: The worker thread scaling change means your process may use more threads than before. If you're running in a thread-constrained environment, setNIKA_WORKER_THREADSto control the count.
[!NOTE]
Media pipeline users: If you had workflows importing files outside the project directory vianika:import, they will now fail withNIKA-291. Move the files into your project or usefetch:to download them instead.
π¦ 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.2 |
| π» | 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.1...v0.68.2