code wiki / _hdl_build / nx_secret_scan_gate.nx

nx_secret_scan_gate.nx source

↩ module page · 221 lines · 9331 B

1// nx_secret_scan_gate.nx -- re-runnable GATE for nx_secret_scan + the self-manage closure. 2// (Engineer verb: VERIFY. Team-built, hardware up -- replaces a bash gate, per operator law: 3// no sh/py; capabilities are built INTO the team.) 4// 5// KATs (fixture work uses an ALT issues log in /tmp so the production signal stream the 6// sentinel watches is never polluted by plants): 7// K1 clean tree -> scan(alt) exit 0 8// K2 planted PW-literal fixture -> scan(alt) exit 1 9// K3 alt issues log NAMES the planted file with the PLAINTEXT-SECRET marker 10// K4 fixture removed -> scan(alt2) exit 0 again (returns-to-green, tree left clean) 11// K5 planted EMPTY literal (PW="" shred idiom) -> exit 0 (intelligence: hygiene != leak) 12// Then the SELF-MANAGE closure: 13// P1 production scan (real issues log). GREEN posture required for gate green. 14// P2 if prod is green and the issues-log count grew past the last SENTINEL-RESOLVE mark, 15// append "SENTINEL-RESOLVE key=PLAINTEXT-SECRET at=<count>" (idempotent: only when 16// count > mark) -- the Engineer's verify IS the resolve evidence. 17// P3 re-run the security sentinel to refresh posture (its verdict is its own; reported). 18// Exit 0 = 4/4 KATs + prod GREEN. license_tier: ORIGINAL 19import "nx_syscalls.nx" 20 21const GG_BUF_MAX: i64 = 4194304 22const AT_FDCWD: i64 = 0 - 100 23 24func _p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 25func _pn(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=48+(m%10);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(1,bb,k); return 0 } 26func gg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 27func gg_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i } 28func gg_unlink(path: *u8) -> i64 { __syscall(263, AT_FDCWD, path, 0, 0, 0, 0) return 0 } 29 30func gg_read(path: *u8, buf: *u8) -> i64 { 31 let fd: i64 = sys_openat_rd(path) 32 if fd < 0 { return 0 - 1 } 33 var n: i64 = 0 34 var go: i64 = 1 35 while go == 1 { 36 let base: i64 = buf as i64 37 let r: i64 = sys_read(fd, (base + n) as *u8, GG_BUF_MAX - n) 38 if r <= 0 { go = 0 } else { n = n + r } 39 if n >= GG_BUF_MAX { go = 0 } 40 } 41 sys_close(fd) 42 return n 43} 44 45func gg_count(buf: *u8, n: i64, pat: *u8) -> i64 { 46 let pl: i64 = gg_slen(pat) 47 if pl == 0 { return 0 } 48 var cnt: i64 = 0 49 var i: i64 = 0 50 while i + pl <= n { 51 var k: i64 = 0 52 var hit: i64 = 1 53 while k < pl { 54 if buf[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } 55 } 56 if hit == 1 { cnt = cnt + 1; i = i + pl } else { i = i + 1 } 57 } 58 return cnt 59} 60 61// highest "<prefix><digits>" in buf; -1 if never seen (same contract as the sentinel's) 62func gg_max_marked(buf: *u8, n: i64, prefix: *u8) -> i64 { 63 let pl: i64 = gg_slen(prefix) 64 var best: i64 = 0 - 1 65 var i: i64 = 0 66 while i + pl <= n { 67 var k: i64 = 0 68 var hit: i64 = 1 69 while k < pl { 70 if buf[i+k] != prefix[k] { hit = 0; k = pl } else { k = k + 1 } 71 } 72 if hit == 1 { 73 var v: i64 = 0 74 var j: i64 = i + pl 75 var digits: i64 = 0 76 var scan: i64 = 1 77 while scan == 1 { 78 if j >= n { scan = 0 } 79 else { 80 let c: i64 = buf[j] as i64 81 if c < 48 { scan = 0 } 82 else { 83 if c > 57 { scan = 0 } 84 else { v = v * 10 + (c - 48); digits = digits + 1; j = j + 1 } 85 } 86 } 87 } 88 if digits > 0 { if v > best { best = v } } 89 i = i + pl 90 } else { i = i + 1 } 91 } 92 return best 93} 94 95// fork+exec child with up to one arg; return RAW-derived exit code (wait4, honest judge) 96func gg_run(path: *u8, a1: *u8) -> i64 { 97 let pid: i64 = sys_fork() 98 if pid == 0 { 99 let argv: *i64 = sys_mmap(64) as *i64 100 argv[0] = path as i64 101 var ai: i64 = 1 102 if (a1 as i64) != 0 { argv[ai] = a1 as i64; ai = ai + 1 } 103 argv[ai] = 0 104 let envp: *i64 = sys_mmap(16) as *i64 105 envp[0] = 0 106 sys_execve(path, argv, envp) 107 sys_exit(127) 108 } 109 let st: *i64 = sys_mmap(16) as *i64 110 sys_wait4(pid, st, 0) 111 let raw: i64 = st[0] 112 if (raw & 0x7f) != 0 { return 128 + (raw & 0x7f) } 113 return (raw >> 8) & 0xff 114} 115 116func gg_write(path: *u8, content: *u8) -> i64 { 117 let fd: i64 = sys_openat_wr(path, 0x1a4) 118 if fd < 0 { return 0 - 1 } 119 sys_write(fd, content, gg_slen(content)) 120 sys_close(fd) 121 return 0 122} 123 124func main() -> i64 { 125 _p("=== SECRET-SCAN GATE: verify the detector, then close the self-manage loop (verb: VERIFY) ===\n" as *u8) 126 let scan: *u8 = "_offc/nx_secret_scan.elf" as *u8 127 let alt: *u8 = "/tmp/gate_issues_secret_scan.log" as *u8 128 let plant: *u8 = "_gate_pw_fixture" as *u8 129 gg_unlink(alt) 130 gg_unlink(plant) 131 var pass: i64 = 0 132 133 // K1: clean tree -> green 134 let rc1: i64 = gg_run(scan, alt) 135 if rc1 == 0 { pass = pass + 1; _p("K1 clean-tree-green OK\n" as *u8) } 136 else { _p("K1 FAIL rc=" as *u8); _pn(rc1); _p("\n" as *u8) } 137 138 // K2: plant a PW literal (assembled at runtime; never a real secret) 139 let fix: *u8 = sys_mmap(128) 140 var o: i64 = 0 141 o = gg_cat(fix, o, "PW=" as *u8); fix[o] = 34 as u8; o = o + 1 142 o = gg_cat(fix, o, "gate_planted_not_a_real_secret" as *u8); fix[o] = 34 as u8; o = o + 1 143 fix[o] = 10 as u8; o = o + 1; fix[o] = 0 as u8 144 gg_write(plant, fix) 145 let rc2: i64 = gg_run(scan, alt) 146 if rc2 == 1 { pass = pass + 1; _p("K2 plant-detected-red OK\n" as *u8) } 147 else { _p("K2 FAIL rc=" as *u8); _pn(rc2); _p("\n" as *u8) } 148 149 // K3: alt issues log names the planted file 150 let buf: *u8 = sys_mmap(GG_BUF_MAX + 64) 151 let an: i64 = gg_read(alt, buf) 152 var named: i64 = 0 153 if an > 0 { named = gg_count(buf, an, "ISSUE PLAINTEXT-SECRET file=_gate_pw_fixture pat=PW-DQUOTE" as *u8) } 154 if named > 0 { pass = pass + 1; _p("K3 plant-named-in-issues OK\n" as *u8) } else { _p("K3 FAIL\n" as *u8) } 155 156 // K4: remove fixture -> green again (tree left clean) 157 gg_unlink(plant) 158 gg_unlink(alt) 159 let rc4: i64 = gg_run(scan, alt) 160 gg_unlink(alt) 161 if rc4 == 0 { pass = pass + 1; _p("K4 returns-to-green OK\n" as *u8) } 162 else { _p("K4 FAIL rc=" as *u8); _pn(rc4); _p("\n" as *u8) } 163 164 // K5: EMPTY literal (the shred-after-use idiom) must NOT count as a credential 165 o = 0 166 o = gg_cat(fix, o, "PW=" as *u8); fix[o] = 34 as u8; o = o + 1; fix[o] = 34 as u8; o = o + 1 167 fix[o] = 10 as u8; o = o + 1; fix[o] = 0 as u8 168 gg_write(plant, fix) 169 let rc5: i64 = gg_run(scan, alt) 170 gg_unlink(plant) 171 gg_unlink(alt) 172 if rc5 == 0 { pass = pass + 1; _p("K5 empty-literal-not-a-finding OK\n" as *u8) } 173 else { _p("K5 FAIL rc=" as *u8); _pn(rc5); _p("\n" as *u8) } 174 175 _p("KAT " as *u8); _pn(pass); _p("/5\n" as *u8) 176 177 // P1: PRODUCTION posture scan (real issues log) 178 let prc: i64 = gg_run(scan, 0 as *u8) 179 if prc == 0 { _p("P1 prod-scan GREEN\n" as *u8) } 180 else { _p("P1 prod-scan RED rc=" as *u8); _pn(prc); _p("\n" as *u8) } 181 182 // P2: idempotent resolve when prod is green and the count outgrew the last mark 183 if prc == 0 { 184 let in_n: i64 = gg_read("knowledge/status/issues_durable.log" as *u8, buf) 185 var cnt: i64 = 0 186 if in_n > 0 { cnt = gg_count(buf, in_n, "ISSUE PLAINTEXT-SECRET" as *u8) } 187 let sn: i64 = gg_read("knowledge/status/security_sentinel.log" as *u8, buf) 188 var mark: i64 = 0 - 1 189 if sn > 0 { mark = gg_max_marked(buf, sn, "SENTINEL-RESOLVE key=PLAINTEXT-SECRET at=" as *u8) } 190 if cnt > mark { 191 let lfd: i64 = sys_openat_append("knowledge/status/security_sentinel.log" as *u8, 0x1a4) 192 if lfd >= 0 { 193 sys_write(lfd, "SENTINEL-RESOLVE key=PLAINTEXT-SECRET at=" as *u8, 41) 194 let nb: *u8 = sys_mmap(28) 195 var m: i64 = cnt; var k: i64 = 0 196 let t: *u8 = sys_mmap(28) 197 if m == 0 { t[0] = 48; k = 1 } 198 while m > 0 { t[k] = 48 + (m % 10); m = m / 10; k = k + 1 } 199 var i: i64 = 0 200 while i < k { nb[i] = t[k-1-i]; i = i + 1 } 201 sys_write(lfd, nb, k) 202 sys_write(lfd, "\n" as *u8, 1) 203 sys_close(lfd) 204 _p("P2 resolve-appended at=" as *u8); _pn(cnt); _p(" (gate-green evidence)\n" as *u8) 205 } 206 } else { 207 _p("P2 resolve-current mark=" as *u8); _pn(mark); _p(" (no append needed)\n" as *u8) 208 } 209 } 210 211 // P3: refresh the sentinel (its verdict is its own; reported, not gated on other rows) 212 let src: i64 = gg_run("_offc/nx_security_sentinel.elf" as *u8, 0 as *u8) 213 _p("P3 sentinel-exit=" as *u8); _pn(src); _p("\n" as *u8) 214 215 var verdict: i64 = 1 216 if pass == 5 { if prc == 0 { verdict = 0 } } 217 if verdict == 0 { _p("GATE-VERDICT secret_scan 5/5+prod GREEN\n" as *u8) } 218 else { _p("GATE-VERDICT secret_scan RED\n" as *u8) } 219 sys_exit(verdict) 220 return verdict 221}