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}