Commit 786a116
fix(v1.100 Amendment 2): code-C — preflight rejects mask-only unit-file (symlink to /dev/null) (#521)
Surfaced by Amendment-2-code-E v2 corrected run on srv3 (2026-04-28T21:30:13Z):
the dispatcher reached G1/AuthorityNFTBan/OrphanProceed PROCEED, code-B
csf.disabled relaxation correctly accepted, §32 step 2 safety-net
inserted, A.1 unmask succeeded — then A.2 ServiceEnable failed with
"Unit file csf.service does not exist" because the canonical unit-file
path (/etc/systemd/system/csf.service) was a mask symlink to /dev/null
with NO real backing unit-file anywhere on the host. Result:
RESTORE_FAILED_EXECUTION at stage=mutate, exit=8, partial mutation
(safety-net retained, csf still unmasked).
This is the third (and final, on the §32 path) implementation gap
surfaced by the destructive-cycle audit chain. Like code-B, the fix is
implementation-class — no contract amendment, no decision-lattice
change, no §32 ordering change.
Surgical patch
==============
In productionPreflightDep.PreflightTarget's unit-file OR-list loop:
- After exec.FileExists returns true for a candidate path, route
through exec.Run("readlink", "-f", <path>) to obtain the resolved
canonical path (read-only per §43.3 raw-Run policy for read-only
probes).
- If resolved == "/dev/null", treat the candidate as not-found and
continue the OR-list. Records a maskOnlyObserved flag.
- A real file (resolved == path) or symlink to a real file
(resolved != /dev/null) is accepted.
- If every candidate is absent: preserve existing
ErrPreflightUnitMissing semantic.
- If at least one candidate was a mask-only symlink and no real
backing path resolved: refuse with new typed sentinel
ErrPreflightUnitFileMaskingOnly.
The new sentinel:
ErrPreflightUnitFileMaskingOnly = errors.New(
"restore preflight: unit file is a mask symlink (points to
/dev/null); no real unit file backs the service")
Existing exit code remains 8. Existing terminal remains
RESTORE_FAILED_EXECUTION. Same hardening applies uniformly to ufw /
firewalld / iptables / csf — every firewall in §18.2.
Tests added (10 new test functions)
===================================
AMD2C-1: regular file at /etc/systemd/system/csf.service → PASS
AMD2C-2: real unit at /usr/lib/systemd/system/csf.service → PASS
AMD2C-3: mask symlink only → REFUSE ErrPreflightUnitFileMaskingOnly
AMD2C-4: mask at /etc/ AND real at /usr/lib/ → PASS (OR-list resolves)
AMD2C-5: plain regular file → PASS
AMD2C-6: symlink to real backing file → PASS
AMD2C-7: ufw / firewalld / iptables mask hardening (3 sub-tests)
AMD2C-8: unknown firewallType → REFUSE ErrPreflightUnknownFirewall
AMD2C-9: NoMutationCalls — readlink only, zero mutation primitives
AMD2C-10: no candidate path at all → REFUSE ErrPreflightUnitMissing
(distinguished from MaskingOnly)
4B-1.7 NoMutationCalls test updated: now allows `readlink` Run calls
(read-only per §43.3) while still asserting ZERO mutation primitives.
Test results (lab4 build host)
==============================
- go test ./cmd/nftban-installer/... -run 'Preflight|RestoreDeps' → PASS
- go test ./cmd/nftban-installer/... → PASS
- go test ./internal/installer/restore/... → PASS
- go test ./... → 64 pkgs PASS, 0 FAIL
Files changed
=============
cmd/nftban-installer/restore_deps.go | +61 / -8 (53 line net)
cmd/nftban-installer/restore_deps_test.go | +252 / -4 (10 new tests + 1 update)
No files outside the operator-allowed two.
Invariants preserved
====================
- §23.1 preflight read-only: PRESERVED — readlink is read-only per §43.3.
- INV-PR26-NEW-MUTATION-SURFACES-BOUNDED: PRESERVED — zero new surfaces.
- §32 ordering: UNTOUCHED — A.1/A.2 not modified; mask detection happens at preflight before §32 step 2.
- §22 / §19.4 terminals + exit codes: UNTOUCHED — same exit=8, same RESTORE_FAILED_EXECUTION.
- Amendment 1 §30.2 CSF-only mutation scope: PRESERVED — non-CSF firewalls get the same mask-symlink hardening but still strict no-.disabled-relaxation.
- §19.2 layer 4 / main.go:132 history gate: UNTOUCHED.
- Existing 4B-1 fixture matrix: behavior unchanged on real-file paths.
- Code-B csf.disabled acceptance: PRESERVED — §54 binary relaxation still fires before unit-file check.
No CI workflow edit. No state machine change. No flag change. No
contract change. No execute.go change. No engine.go change. No
main.go change.
No host action.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 48b9e50 commit 786a116
2 files changed
Lines changed: 301 additions & 12 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
| |||
160 | 161 | | |
161 | 162 | | |
162 | 163 | | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
163 | 177 | | |
164 | 178 | | |
165 | 179 | | |
| |||
214 | 228 | | |
215 | 229 | | |
216 | 230 | | |
217 | | - | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
218 | 250 | | |
| 251 | + | |
219 | 252 | | |
220 | | - | |
221 | | - | |
222 | | - | |
| 253 | + | |
| 254 | + | |
223 | 255 | | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
224 | 270 | | |
225 | 271 | | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
226 | 279 | | |
227 | 280 | | |
228 | 281 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
444 | 444 | | |
445 | 445 | | |
446 | 446 | | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
452 | 457 | | |
453 | 458 | | |
454 | 459 | | |
| |||
1050 | 1055 | | |
1051 | 1056 | | |
1052 | 1057 | | |
1053 | | - | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
1054 | 1061 | | |
1055 | 1062 | | |
1056 | 1063 | | |
1057 | 1064 | | |
1058 | 1065 | | |
1059 | 1066 | | |
1060 | 1067 | | |
1061 | | - | |
1062 | | - | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
| 1131 | + | |
| 1132 | + | |
| 1133 | + | |
| 1134 | + | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
| 1140 | + | |
| 1141 | + | |
| 1142 | + | |
| 1143 | + | |
| 1144 | + | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
| 1206 | + | |
| 1207 | + | |
| 1208 | + | |
| 1209 | + | |
| 1210 | + | |
| 1211 | + | |
| 1212 | + | |
| 1213 | + | |
| 1214 | + | |
| 1215 | + | |
| 1216 | + | |
| 1217 | + | |
| 1218 | + | |
| 1219 | + | |
| 1220 | + | |
| 1221 | + | |
| 1222 | + | |
| 1223 | + | |
| 1224 | + | |
| 1225 | + | |
| 1226 | + | |
| 1227 | + | |
| 1228 | + | |
| 1229 | + | |
| 1230 | + | |
| 1231 | + | |
| 1232 | + | |
| 1233 | + | |
| 1234 | + | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
| 1257 | + | |
| 1258 | + | |
| 1259 | + | |
| 1260 | + | |
| 1261 | + | |
| 1262 | + | |
| 1263 | + | |
| 1264 | + | |
| 1265 | + | |
| 1266 | + | |
| 1267 | + | |
| 1268 | + | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
| 1282 | + | |
| 1283 | + | |
| 1284 | + | |
| 1285 | + | |
| 1286 | + | |
| 1287 | + | |
| 1288 | + | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
| 1295 | + | |
| 1296 | + | |
| 1297 | + | |
| 1298 | + | |
1063 | 1299 | | |
1064 | 1300 | | |
0 commit comments