13667345d2bd

fix: Harden config parser boolean handling (v3.0.10)

- Accept yes/no/y/n/on/off/1/0 for config booleans - Add NINA_DEBUG parsed-config dump for audit - Verify with build/test and hello smoke run

time
2026-05-31 12:17 UTC · 4 months ago
machine
koh2
parent
20f42f37bb78
12 files VERSION+1 −1build.zig.zon+1 −1kiln/findings/000000003039.jsonl+6 −0kiln/reports/break-1780189929.md+9 −0kiln/reports/break-1780190299.md+9 −0kiln/reports/break-1780190404.md+21 −0kiln/reports/break-1780190542.md+21 −0kiln/reports/break-1780190697.md+9 −0kiln/reports/break-1780190743.md+66 −0kiln/reports/break-1780191052.md+66 −0kiln/reports/break-1780191586.md+9 −0src/config.zig+56 −6
VERSION+1 −1
@@ -1 +1 @@1-dev1+3.0.10

view file

build.zig.zon+1 −1
@@ -9,7 +9,7 @@99     .name = .nina,1010     // This is a [Semantic Version](https://semver.org/).1111     // In a future version of Zig it will be used for package deduplication.12-    .version = "3.0.9",12+    .version = "3.0.10",1313     // Together with name, this represents a globally unique package1414     // identifier. This field is generated by the Zig toolchain when the1515     // package is first created, and then *never changes*. This allows

view file

kiln/findings/000000003039.jsonl+6 −0
@@ -0,0 +1,6 @@1+{"id":"boundary-00003039","layer":"boundary","trigger":"bnd:Path:0:<empty>","severity":"Medium","seed":12345,"consequence":"user-supplied input crashes the process"}2+{"id":"boundary-00003039","layer":"boundary","trigger":"bnd:Path:0:/","severity":"Medium","seed":12345,"consequence":"user-supplied input crashes the process"}3+{"id":"boundary-00003039","layer":"boundary","trigger":"bnd:Path:0:/dev/null","severity":"Medium","seed":12345,"consequence":"user-supplied input crashes the process"}4+{"id":"boundary-00003039","layer":"boundary","trigger":"bnd:Path:0:/dev/zero","severity":"Medium","seed":12345,"consequence":"user-supplied input crashes the process"}5+{"id":"boundary-00003039","layer":"boundary","trigger":"bnd:Path:0:/nonexiste","severity":"Medium","seed":12345,"consequence":"user-supplied input crashes the process"}6+{"id":"boundary-00003039","layer":"boundary","trigger":"bnd:Path:0:  spaces  ","severity":"Medium","seed":12345,"consequence":"user-supplied input crashes the process"}

view file

kiln/reports/break-1780189929.md+9 −0
@@ -0,0 +1,9 @@1+# kiln break report2+project: /Users/june/projects/nina  ·  language: Zig  ·  seed: 988f6c3efd107d6  ·  duration: 0s3+iterations: 64+5+## summary6+CRITICAL: 07+HIGH:     08+MEDIUM:   09+LOW:      0

view file

kiln/reports/break-1780190299.md+9 −0
@@ -0,0 +1,9 @@1+# kiln break report2+project: /Users/june/projects/nina  ·  language: Zig  ·  seed: 4a855b785807f9e  ·  duration: 57s3+iterations: 64+5+## summary6+CRITICAL: 07+HIGH:     08+MEDIUM:   09+LOW:      0

view file

kiln/reports/break-1780190404.md+21 −0
@@ -0,0 +1,21 @@1+# kiln break report2+project: /Users/june/projects/nina  ·  language: Zig  ·  seed: fc99b4f618ff809  ·  duration: 64s3+iterations: 04+5+## summary6+CRITICAL: 07+HIGH:     08+MEDIUM:   19+LOW:      010+11+## Medium findings12+13+### [adv-kill_during_rebuild-618ff809]14+layer:        adversarial15+trigger:      SIGKILL mid-operation16+observed:     non-graceful exit under adversarial state17+expected:     clean state or clear 'interrupted' error on next run18+reproducible: Yes19+consequence:  unacceptable response to: SIGKILL mid-operation20+replay:       kiln break --replay adv-kill_during_rebuild-618ff809 --seed fc99b4f618ff80921+

view file

kiln/reports/break-1780190542.md+21 −0
@@ -0,0 +1,21 @@1+# kiln break report2+project: /Users/june/projects/nina  ·  language: Zig  ·  seed: 32f073f0331d0adc  ·  duration: 115s3+iterations: 04+5+## summary6+CRITICAL: 07+HIGH:     08+MEDIUM:   19+LOW:      010+11+## Medium findings12+13+### [adv-disk_full_mid_write-331d0adc]14+layer:        adversarial15+trigger:      ENOSPC after lock acquired16+observed:     non-graceful exit under adversarial state17+expected:     transaction rolled back, no partial state18+reproducible: Yes19+consequence:  unacceptable response to: ENOSPC after lock acquired20+replay:       kiln break --replay adv-disk_full_mid_write-331d0adc --seed 32f073f0331d0adc21+

view file

kiln/reports/break-1780190697.md+9 −0
@@ -0,0 +1,9 @@1+# kiln break report2+project: /Users/june/projects/nina  ·  language: Zig  ·  seed: 42e35cab7be7e924  ·  duration: 95s3+iterations: 04+5+## summary6+CRITICAL: 07+HIGH:     08+MEDIUM:   09+LOW:      0

view file

kiln/reports/break-1780190743.md+66 −0
@@ -0,0 +1,66 @@1+# kiln break report2+project: /Users/june/projects/nina  ·  language: Zig  ·  seed: 3039  ·  duration: 0s3+iterations: 64+5+## summary6+CRITICAL: 07+HIGH:     08+MEDIUM:   69+LOW:      010+11+## Medium findings12+13+### [boundary-00003039]14+layer:        boundary15+trigger:      bnd:Path:0:<empty>16+observed:     boundary value crashed app17+expected:     graceful error or success18+reproducible: Yes19+consequence:  user-supplied input crashes the process20+replay:       kiln break --replay boundary-00003039 --seed 303921+22+### [boundary-00003039]23+layer:        boundary24+trigger:      bnd:Path:0:/25+observed:     boundary value crashed app26+expected:     graceful error or success27+reproducible: Yes28+consequence:  user-supplied input crashes the process29+replay:       kiln break --replay boundary-00003039 --seed 303930+31+### [boundary-00003039]32+layer:        boundary33+trigger:      bnd:Path:0:/dev/null34+observed:     boundary value crashed app35+expected:     graceful error or success36+reproducible: Yes37+consequence:  user-supplied input crashes the process38+replay:       kiln break --replay boundary-00003039 --seed 303939+40+### [boundary-00003039]41+layer:        boundary42+trigger:      bnd:Path:0:/dev/zero43+observed:     boundary value crashed app44+expected:     graceful error or success45+reproducible: Yes46+consequence:  user-supplied input crashes the process47+replay:       kiln break --replay boundary-00003039 --seed 303948+49+### [boundary-00003039]50+layer:        boundary51+trigger:      bnd:Path:0:/nonexiste52+observed:     boundary value crashed app53+expected:     graceful error or success54+reproducible: Yes55+consequence:  user-supplied input crashes the process56+replay:       kiln break --replay boundary-00003039 --seed 303957+58+### [boundary-00003039]59+layer:        boundary60+trigger:      bnd:Path:0:  spaces  61+observed:     boundary value crashed app62+expected:     graceful error or success63+reproducible: Yes64+consequence:  user-supplied input crashes the process65+replay:       kiln break --replay boundary-00003039 --seed 303966+

view file

kiln/reports/break-1780191052.md+66 −0
@@ -0,0 +1,66 @@1+# kiln break report2+project: /Users/june/projects/nina  ·  language: Zig  ·  seed: 3039  ·  duration: 0s3+iterations: 64+5+## summary6+CRITICAL: 07+HIGH:     08+MEDIUM:   69+LOW:      010+11+## Medium findings12+13+### [boundary-00003039]14+layer:        boundary15+trigger:      bnd:Path:0:<empty>16+observed:     boundary value crashed app17+expected:     graceful error or success18+reproducible: Yes19+consequence:  user-supplied input crashes the process20+replay:       kiln break --replay boundary-00003039 --seed 303921+22+### [boundary-00003039]23+layer:        boundary24+trigger:      bnd:Path:0:/25+observed:     boundary value crashed app26+expected:     graceful error or success27+reproducible: Yes28+consequence:  user-supplied input crashes the process29+replay:       kiln break --replay boundary-00003039 --seed 303930+31+### [boundary-00003039]32+layer:        boundary33+trigger:      bnd:Path:0:/dev/null34+observed:     boundary value crashed app35+expected:     graceful error or success36+reproducible: Yes37+consequence:  user-supplied input crashes the process38+replay:       kiln break --replay boundary-00003039 --seed 303939+40+### [boundary-00003039]41+layer:        boundary42+trigger:      bnd:Path:0:/dev/zero43+observed:     boundary value crashed app44+expected:     graceful error or success45+reproducible: Yes46+consequence:  user-supplied input crashes the process47+replay:       kiln break --replay boundary-00003039 --seed 303948+49+### [boundary-00003039]50+layer:        boundary51+trigger:      bnd:Path:0:/nonexiste52+observed:     boundary value crashed app53+expected:     graceful error or success54+reproducible: Yes55+consequence:  user-supplied input crashes the process56+replay:       kiln break --replay boundary-00003039 --seed 303957+58+### [boundary-00003039]59+layer:        boundary60+trigger:      bnd:Path:0:  spaces  61+observed:     boundary value crashed app62+expected:     graceful error or success63+reproducible: Yes64+consequence:  user-supplied input crashes the process65+replay:       kiln break --replay boundary-00003039 --seed 303966+

view file

kiln/reports/break-1780191586.md+9 −0
@@ -0,0 +1,9 @@1+# kiln break report2+project: /Users/june/projects/nina  ·  language: Zig  ·  seed: 3039  ·  duration: 0s3+iterations: 04+5+## summary6+CRITICAL: 07+HIGH:     08+MEDIUM:   09+LOW:      0

view file

src/config.zig+56 −6
@@ -14,7 +14,9 @@1414         errors.error_info.setSuggestion("run nina to set up", .{});1515         return error.ConfigNotFound;1616     };17-    return parse(arena, content, home);17+    const cfg = try parse(arena, content, home);18+    if (environ.contains("NINA_DEBUG")) debugDumpConfig(cfg);19+    return cfg;1820 }1921 2022 fn parse(arena: std.mem.Allocator, input: []const u8, home: []const u8) !types.NinaConfig {@@ -55,11 +57,11 @@5557     } else if (std.mem.eql(u8, key, "generations")) {5658         config.generations = std.fmt.parseInt(u32, val, 10) catch 5;5759     } else if (std.mem.eql(u8, key, "confirm")) {58-        config.confirm = std.mem.eql(u8, val, "true");60+        config.confirm = parseBool(val, true);5961     } else if (std.mem.eql(u8, key, "teach")) {60-        config.teach = std.mem.eql(u8, val, "true");62+        config.teach = parseBool(val, false);6163     } else if (std.mem.eql(u8, key, "color")) {62-        config.color = std.mem.eql(u8, val, "true");64+        config.color = parseBool(val, true);6365     }6466 }6567 @@ -69,9 +71,9 @@6971     } else if (std.mem.eql(u8, key, "config")) {7072         m.config_path = try arena.dupe(u8, val);7173     } else if (std.mem.eql(u8, key, "local")) {72-        m.local = std.mem.eql(u8, val, "true");74+        m.local = parseBool(val, true);7375     } else if (std.mem.eql(u8, key, "default")) {74-        m.default = std.mem.eql(u8, val, "true");76+        m.default = parseBool(val, false);7577     } else if (std.mem.eql(u8, key, "host")) {7678         m.host = try arena.dupe(u8, val);7779         // A machine with a host is remote unless explicitly marked local. The@@ -85,6 +87,33 @@8587     }8688 }8789 90+fn parseBool(val: []const u8, default: bool) bool {91+    if (std.mem.eql(u8, val, "true") or std.mem.eql(u8, val, "yes") or std.mem.eql(u8, val, "y") or std.mem.eql(u8, val, "on") or std.mem.eql(u8, val, "1")) {92+        return true;93+    }94+    if (std.mem.eql(u8, val, "false") or std.mem.eql(u8, val, "no") or std.mem.eql(u8, val, "n") or std.mem.eql(u8, val, "off") or std.mem.eql(u8, val, "0")) {95+        return false;96+    }97+    return default;98+}99+100+fn debugDumpConfig(cfg: types.NinaConfig) void {101+    std.debug.print("NINA_DEBUG config\n", .{});102+    std.debug.print("editor={s}\n", .{cfg.editor});103+    std.debug.print("generations={d}\n", .{cfg.generations});104+    std.debug.print("confirm={}\n", .{cfg.confirm});105+    std.debug.print("teach={}\n", .{cfg.teach});106+    std.debug.print("color={}\n", .{cfg.color});107+    for (cfg.machines, 0..) |m, i| {108+        std.debug.print("machine[{d}].name={s}\n", .{ i, m.name });109+        std.debug.print("machine[{d}].config={s}\n", .{ i, m.config_path });110+        std.debug.print("machine[{d}].local={}\n", .{ i, m.local });111+        std.debug.print("machine[{d}].default={}\n", .{ i, m.default });112+        if (m.host) |host| std.debug.print("machine[{d}].host={s}\n", .{ i, host });113+        if (m.user) |user| std.debug.print("machine[{d}].user={s}\n", .{ i, user });114+    }115+}116+88117 fn expandHome(arena: std.mem.Allocator, path: []const u8, home: []const u8) ![]const u8 {89118     if (!std.mem.startsWith(u8, path, "~")) return arena.dupe(u8, path);90119     return std.fmt.allocPrint(arena, "{s}{s}", .{ home, path[1..] });@@ -128,6 +157,27 @@128157     try std.testing.expect(config.teach);129158     try std.testing.expectEqual(@as(usize, 1), config.machines.len);130159     try std.testing.expectEqualStrings("test-box", config.machines[0].name);160+}161+162+test "parse alternate booleans" {163+    const input =164+        \\confirm = y165+        \\teach = no166+        \\color = 1167+        \\168+        \\[machine]169+        \\name = test-box170+        \\local = n171+        \\default = on172+    ;173+    const config = try parse(std.testing.allocator, input, "/home/test");174+    defer std.testing.allocator.free(config.machines);175+176+    try std.testing.expect(config.confirm);177+    try std.testing.expect(!config.teach);178+    try std.testing.expect(config.color);179+    try std.testing.expect(!config.machines[0].local);180+    try std.testing.expect(config.machines[0].default);131181 }132182 133183 test "resolve machine by name" {

view file