Repository navigation
v0.5.0
Changed
-
Kubeconfig YAML parsing switched from
guanchzhou/zig-yamlto
sakakibara/yaml-zig, and the
hand-written tree walkers replaced with comptime typed decoding (net -244
lines). Upstreamkubkon/zig-yamlhas been unmaintained since 2026-01, so the
fork inherited its bugs permanently and every future Zig migration by hand.
The replacement is 0.16-native, actively developed, and additionally supports
anchors, aliases and merge keys, which the fork rejects outright.The public API is unchanged —
Cluster/Context/User/Kubeconfig, the
lookup accessors, anddeinit(allocator)all keep their signatures.Kubeconfignow owns a heap-allocated arena and frees its whole graph at
once, replacing ~90 lines of per-field frees and errdefer unwinding.deinit
still accepts an allocator for compatibility but ignores it in favour of the
arena's ownchild_allocator, so a mismatched allocator can no longer
corrupt the free.
Fixed
- Double free in
loadInClusterConfig. Anerrdefer allocator.free(token)was
paired with an unconditionalallocator.free(token)a few lines later, so any
later failure unwound the errdefer and freed the same allocation twice. The
error.ServiceAccountCANotFoundpath immediately below it is reachable in a real
pod with noca.crt. - Use-after-free of the watch
resourceVersion. A BOOKMARK stored a slice that
pointed into the event's parse arena and then freed that arena, leaving a dangling
value that was read by the informer and interpolated into the next watch URL — so a
garbageresourceVersionwas sent to the API server.Watchernow owns a duped
copy and exposesdeinit()to release it; the borrowed initial value from
WatchOptionsis never freed. - Uninitialised read on every watch event.
WatchEnvelope.objectwas
T = undefinedand the informer dereferencedevent.object.metadata.namefor all
event types. It is now?T = null. - A single bookmark tore down the whole watch.
ObjectMeta.nameis required, but
a BOOKMARK's object carries onlyresourceVersionand an ERROR's object is a
Status, so neither binds toT— andallow_watch_bookmarksdefaults to true.
Events are now dispatched on a type-only envelope first, and a single malformed
object is logged and skipped instead of killing the stream. - Deadlock in the exec credential plugin.
.stderr = .pipewas requested and
never drained, so a plugin writing past the pipe buffer (~64 KiB, easily reached by
aws eks get-tokenemitting warnings) blocked on write while the client blocked in
child.wait(). stderr is now inherited, and a guardederrdeferreaps the child on
earlier failure paths. - Watch and pod-stream query parameters are percent-encoded. The 0.4.0 encoding
fix reachedListOptionsbut notwatch,exec,attachorport-forward, which
kept hand-building their query strings. A set-based selector from
LabelSelector.addIn("app in (a,b)") produced a malformed request line, so the
library's own selector API could not be used with its own watch. For the pod streams
it was also an injection: a command endingls&stdin=trueturned on stdin even
though the caller had not asked for it. emit_null_optional_fieldsis now false for resource, patch and cache
serialization. Since every Kubernetes type is?T = null, bodies were mostly nulls.
JsonPatch.buildemitted{"op":"remove","path":"/x","value":null,"from":null},
which RFC 6902 forbids and strict implementations reject.- A
Statuswithoutcodeno longer makes 404s and 403s retry. The code fell
back tonull, whichretry.shouldRetrycannot distinguish from a transport
failure, so such responses were retried the full budget with backoff. The HTTP
status is now used as the fallback. insecure-skip-tls-verifyis no longer silently dropped. The old
parseClusteraccepted only a.booleanYAML value, but zig-yaml built
.booleansolely on its stringify path and never when parsing, sotrue
arrived as the scalar"true"and was discarded. Still masked downstream by
theinsecure_skip_verifyguard inclient.zig, which reports it as
unsupported becausestd.http.Clientdoes not expose TLS internals.
Notes
zig build test-fuzz --fuzzdoes not compile on Zig 0.16.0 due to a bug in
the toolchain's owncompiler/test_runner.zig(*builtin.StackTracevs
*const debug.StackTrace), unrelated to this change and reproducible on an
unmodified tree. The single-shot fuzz run underzig build teststill works.
Removed
- Dead code, each verified unreferenced across
src/,tests/,docs/and the
README before deletion:client.KubeConfig(superseded by
kubeconfig_yaml.Kubeconfig),tls.readFileToAlloc(whose doc comment cited two
functions that do not exist), andretry.retryWithBackoff(which duplicated
sendWithRetry's loop and tookoperation: anytypebut invoked it with no
arguments). version/versionString()inklient.zigreported0.1.0-alphawhile the
manifest declared0.4.0— three releases stale, in two hand-maintained copies.
Both must now be bumped alongsidebuild.zig.zon; there is no compile-time link.
Build
test-comprehensiveand the WebSocket integration test now actually compile.
The former was_ = b.step(...)— a step with no dependencies, so it built and ran
nothing — and the latter was never referenced bybuild.zigat all. Between them
1,933 lines of test code had silently stopped compiling against Zig 0.16. Both are
wired up, and the suites migrated to the 0.16 APIs (std.process.run,
std.heap.DebugAllocator, and threadingstd.Io). They still require a live
cluster to run; compiling them in CI is what stops them rotting again.