decoy sweep: eight holes in this repo's gates, all measured, all fixed (R-421)
gates / gates (push) Failing after 13s

Every gate was DECOYED - the label constructed without the fact, the gate run, the verdict recorded.
No verdict here was reached by reading, because reading is exactly how the five prior instances hid.

SCOPE IS A FACT TOO, and it was the big one. Six gates decided what to look at with os.listdir - one
directory level. Every one was green AND CORRECT, because no template subdirectory exists today; every
one would have gone blind the moment anyone added templates/partials/, which is an ordinary act. A
single planted file carrying an emoji, a native confirm(), hand-rolled row markup, a dangling JS id
reference, a templated secret and an unregistered retrieval promise passed all six.

THE CONTROL IS WHAT MAKES THAT A MEASUREMENT: mojibake and docker-v already used os.walk, saw the
identical planted file, and convicted. So the cause was the listing, not the decoy.

COMMENTS ARE NOT CODE, AND COMMENTS ARE NOT CONTROLS. debug-routes matched `case subpath == "x"` in
raw text, so a case left in a commented-out block counted as a live handler - which is R-400's
original defect (seven dead controls on the page an operator opens when something is already wrong)
reached through the one door its own gate could not see. app-row-dedup's MUST_USE check had the same
shape: a commented-out {{template "app_list_row"}} satisfied it.

Stripping is deliberately crude in debug_route_gate, and that is correct there: its own docstring
insists on ten lines that cannot rot. A // inside a string literal truncates that line, which can
only ever HIDE a reference, never invent one - it fails in the safe direction.

NOT FIXED, and left open with its decoy rather than quietly patched: R-425, offbox-rename scans a
fixed three-entry FILES list, so banned NAS branding in a NEW offbox template passes. The scope was
correct when written and silently narrows every time the feature grows a file.

test_gate_decoys.py holds 10 decoys and declares COVERS, which felhom.eu's new decoy-coverage gate
AST-parses - a substring search for coverage would be the very shape this sweep exists to find.

No Go code. No version bump. No image. No golden owed.
Survey: felhom.eu/documentation/audits/AUDIT-gate-decoys-2026-09-01.md
This commit is contained in:
2026-09-01 12:39:17 +02:00
parent 3db62fc6b2
commit 681cc663ef
10 changed files with 321 additions and 19 deletions
+22
View File
@@ -54,3 +54,25 @@ a probe stayed in D state 3m50s after kill -9; a buffered write with no fsync bl
needs journal access); and statfs/getdents returned HEALTHY on a namespace that EIOs every byte —
fast, and wrong.
-->
## A gate ships with a decoy test that has been seen to fail (R-421)
**A decoy is the LABEL without the FACT** — a directory with the right name and no bake log, a
handler case that exists only in a comment, a note whose prose mentions the marker it lacks. Write
one for every new gate, run it, and watch it convict. `scripts/decoy_coverage_gate.py` refuses a gate
registered without one, or without a named exemption carrying its row.
**Earned by five instances, every one found by accident:** R-410, R-400, R-378, R-419, R-94. The
2026-09-01 sweep read all 29 gate scripts and fooled 16 of them. The four shapes to test against:
1. **name-for-fact** — it matches a path or directory NAME while the fact lives inside the file.
2. **substring-for-field** — it matches a token anywhere in a body instead of in the field carrying it.
3. **declaration-for-reachability** — it checks a thing is declared, not that it RESOLVES.
4. **constant-for-measurement** — it compares a value against itself.
**Scope is a fact too.** Eight of the sixteen were `os.listdir` (one level) where `os.walk` was meant:
green and correct today, blind the moment anyone adds a subdirectory. Prefer `os.walk`, and prefer a
glob over a hand-maintained list of files.
**A decoy nobody would write proves nothing** — say the gate is sound and move on. Five of mine were
withdrawn as illegitimate and are named in `documentation/audits/AUDIT-gate-decoys-2026-09-01.md`.
+28
View File
@@ -1,3 +1,31 @@
## the decoy sweep — can this gate be fooled by a label? (2026-09-01, R-421) — NOT A RELEASE
**No product code, no version bump, no image, no golden.** A scripts change is not a release.
Four times in one week a gate turned out to match a NAME instead of the thing it named — R-410 (a
`mkdir` turned the release gate green), R-400 (seven debug controls answering nothing), R-378 (a
status word inside a sentence), R-419 (a phrase inside prose, including prose saying the marker was
absent). **All four found by accident.** The gates enforce everything else here and were the one part
nothing had checked.
**All 29 gate scripts read and decoyed. 16 were fooled.** 10 fixed here, 4 left with rows
(R-422..R-425), 6 could not be given a plausible decoy and are named (R-426 group d).
**The largest single cause was mundane:** eight gates set their SCOPE with `os.listdir` (one level).
Green and correct today; blind the moment anyone adds `templates/partials/`. `mojibake` and
`docker-v` already used `os.walk`, caught the identical planted file, and are the control that
proves the cause was the listing rather than the decoy.
Full survey table, and the five decoys withdrawn as illegitimate (mine, named):
`documentation/audits/AUDIT-gate-decoys-2026-09-01.md`.
**In this repo:** six gates (`emoji`, `native-confirm`, `app-row-dedup`, `template-id`,
`secret-markup`, `retrieval-promise`) now walk instead of listing one directory; `debug-routes` and
`app-row-dedup` strip comments before matching — a dispatcher case left in a commented-out block
counted as a live handler, which is R-400 reached through the one door its own gate could not see.
New: `controller/scripts/test_gate_decoys.py` (10 decoys). R-425 (`offbox-rename`'s fixed FILES list)
is left OPEN with its decoy recorded rather than quietly fixed.
## scripts — the golden NOTICE, where the debt is created (2026-09-01, R-404) — NOT A RELEASE
**No version heading on purpose.** This changes no Go code, builds no image and bumps nothing. Giving
+13 -3
View File
@@ -49,8 +49,13 @@ if not os.path.isdir(TPL):
print("run from controller/ (internal/web/templates not found)")
sys.exit(2)
files = {f: io.open(os.path.join(TPL, f), encoding="utf-8").read()
for f in sorted(os.listdir(TPL)) if f.endswith(".html")}
_paths = []
for _dp, _dirs, _names in os.walk(TPL): # R-421: any depth, was os.listdir
for _f in sorted(_names):
if _f.endswith('.html'):
_paths.append(os.path.join(_dp, _f))
files = {os.path.relpath(p, TPL): io.open(p, encoding='utf-8').read()
for p in sorted(_paths)}
if "app_row.html" not in files:
failures.append("templates/app_row.html is missing — the canonical row partial")
@@ -70,8 +75,13 @@ for fname, pat, why in FORBIDDEN:
if re.search(pat, src):
failures.append("%s: forbidden old structure %r survives (%s)" % (fname, pat, why))
# R-421 (2026-09-01): a commented-out partial call is not a render. Measured — replacing the real
# call with `<!-- was: {{template "app_list_row" . }} -->` satisfied this check while the surface
# hand-rolled its own row again, which is the exact defect the gate exists to extinguish.
HTML_COMMENT_RE = re.compile(r"<!--.*?-->", re.S)
for fname in MUST_USE:
if '{{template "app_list_row"' not in files.get(fname, ""):
if '{{template "app_list_row"' not in HTML_COMMENT_RE.sub("", files.get(fname, "")):
failures.append("%s: does not render through the app_list_row partial" % fname)
if failures:
+26 -2
View File
@@ -31,6 +31,30 @@ REF_RE = re.compile(r"/api/debug/([A-Za-z0-9/_-]+)")
CASE_RE = re.compile(r'subpath\s*==\s*"([A-Za-z0-9/_-]+)"')
# R-421 (2026-09-01) — COMMENTS ARE NOT CODE, AND COMMENTS ARE NOT CONTROLS.
#
# Both sides of this gate were plain regexes over raw file text, so a `case subpath == "x":` left
# behind in a commented-out block counted as a live handler, and a `/api/debug/x` inside an HTML
# comment counted as a live control. Measured 2026-09-01: commenting out one dispatcher case while
# adding the matching button made this gate report OK on a control that does nothing — which is
# R-400's original defect, reachable again through the one door the gate could not see.
#
# Stripping is deliberately crude and that is correct here: this gate's own docstring insists on ten
# lines of logic that cannot rot. A `//` inside a string literal (a URL, say) would truncate that
# line — which can only ever HIDE a reference, never invent one, so it fails in the safe direction.
GO_COMMENT_RE = re.compile(r"//[^\n]*")
GO_BLOCK_RE = re.compile(r"/\*.*?\*/", re.S)
HTML_COMMENT_RE = re.compile(r"<!--.*?-->", re.S)
def strip_go_comments(src):
return GO_COMMENT_RE.sub("", GO_BLOCK_RE.sub("", src))
def strip_html_comments(src):
return HTML_COMMENT_RE.sub("", src)
def read(path):
if not os.path.exists(path):
print("DEBUG ROUTE GATE INCONCLUSIVE: %s not found (run from controller/)" % path)
@@ -40,8 +64,8 @@ def read(path):
def main():
# Sets, not lists: the same address referenced by two controls is satisfied by one case (§8).
refs = set(REF_RE.findall(read(TEMPLATE)))
cases = set(CASE_RE.findall(read(DISPATCH)))
refs = set(REF_RE.findall(strip_html_comments(read(TEMPLATE))))
cases = set(CASE_RE.findall(strip_go_comments(read(DISPATCH))))
dead = sorted(refs - cases)
unreached = sorted(cases - refs)
+16 -4
View File
@@ -49,13 +49,25 @@ def scan(path):
return hits
def _html_files(root):
# R-421 (2026-09-01): AT ANY DEPTH. This was `os.listdir`, one level only. There are no
# template subdirectories today, so the gate was green and correct — and would have stayed
# green the moment anyone added `templates/partials/`, which is an ordinary thing to do.
# Measured: a planted template in a new partials/ directory passed every listdir-based gate
# and was caught by the two that already used os.walk. See AUDIT-gate-decoys-2026-09-01.md.
out = []
for dirpath, _dirs, names in os.walk(root):
for fn in sorted(names):
if fn.endswith('.html'):
out.append(os.path.join(dirpath, fn))
return sorted(out)
def main():
total = 0
for root in ROOTS:
for fn in sorted(os.listdir(root)):
if not fn.endswith(".html"):
continue
path = os.path.join(root, fn)
for path in _html_files(root):
fn = os.path.relpath(path, root)
for lineno, ch, name in scan(path):
total += 1
print("%s:%d %s %s" % (fn, lineno, ch, name))
+16 -4
View File
@@ -22,13 +22,25 @@ ROOTS = [
NATIVE = re.compile(r"(?<![A-Za-z0-9_$])(?:confirm|prompt|alert)\(\s*[^)\s]")
def _html_files(root):
# R-421 (2026-09-01): AT ANY DEPTH. This was `os.listdir`, one level only. There are no
# template subdirectories today, so the gate was green and correct — and would have stayed
# green the moment anyone added `templates/partials/`, which is an ordinary thing to do.
# Measured: a planted template in a new partials/ directory passed every listdir-based gate
# and was caught by the two that already used os.walk. See AUDIT-gate-decoys-2026-09-01.md.
out = []
for dirpath, _dirs, names in os.walk(root):
for fn in sorted(names):
if fn.endswith('.html'):
out.append(os.path.join(dirpath, fn))
return sorted(out)
def main():
total = 0
for root in ROOTS:
for fn in sorted(os.listdir(root)):
if not fn.endswith(".html"):
continue
path = os.path.join(root, fn)
for path in _html_files(root):
fn = os.path.relpath(path, root)
for lineno, line in enumerate(io.open(path, encoding="utf-8"), 1):
if NATIVE.search(line):
total += 1
+13 -1
View File
@@ -92,9 +92,21 @@ TEMPLATE_COMMENT = re.compile(r"\{\{/\*.*?\*/\}\}", re.S)
GO_COMMENT = re.compile(r"//[^\n]*|/\*.*?\*/", re.S)
# R-421 (2026-09-01): any depth, was os.listdir (one level). No template subdirectory exists
# today, so this was green and correct — and would have stayed green the moment anyone added
# templates/partials/. Measured: a planted file there passed every listdir-based gate.
def _html_at_any_depth(root):
out = []
for dirpath, _dirs, names in os.walk(root):
for fn in sorted(names):
if fn.endswith('.html'):
out.append(os.path.join(dirpath, fn))
return sorted(out)
def scan():
convictions, seen_keys = [], set()
files = sorted(f for f in os.listdir(TEMPLATES) if f.endswith(".html"))
files = [os.path.relpath(x, TEMPLATES) for x in _html_at_any_depth(TEMPLATES)]
sources = [(f, os.path.join(TEMPLATES, f), TEMPLATE_COMMENT) for f in files]
for gp in GO_SOURCES:
if not os.path.exists(gp):
+13 -1
View File
@@ -96,11 +96,23 @@ def check(path):
return convictions
# R-421 (2026-09-01): any depth, was os.listdir (one level). No template subdirectory exists
# today, so this was green and correct — and would have stayed green the moment anyone added
# templates/partials/. Measured: a planted file there passed every listdir-based gate.
def _html_at_any_depth(root):
out = []
for dirpath, _dirs, names in os.walk(root):
for fn in sorted(names):
if fn.endswith('.html'):
out.append(os.path.join(dirpath, fn))
return sorted(out)
def main():
if not os.path.isdir(TPL):
print("secret-in-markup gate INCONCLUSIVE: template dir not found: %s" % TPL)
return 2
files = sorted(f for f in os.listdir(TPL) if f.endswith(".html"))
files = [os.path.relpath(x, TPL) for x in _html_at_any_depth(TPL)]
if not files:
print("secret-in-markup gate INCONCLUSIVE: no templates found in %s" % TPL)
return 2
+14 -4
View File
@@ -72,13 +72,23 @@ def check(path):
return problems
# R-421 (2026-09-01): any depth, was os.listdir (one level). No template subdirectory exists
# today, so this was green and correct — and would have stayed green the moment anyone added
# templates/partials/. Measured: a planted file there passed every listdir-based gate.
def _html_at_any_depth(root):
out = []
for dirpath, _dirs, names in os.walk(root):
for fn in sorted(names):
if fn.endswith('.html'):
out.append(os.path.join(dirpath, fn))
return sorted(out)
def main():
bad = []
for root in ROOTS:
for fn in sorted(os.listdir(root)):
if not fn.endswith(".html"):
continue
bad += check(os.path.join(root, fn))
for _p in _html_at_any_depth(root):
bad += check(_p)
if bad:
print("INTEGRITY GATE FAILED (%d):" % len(bad))
for b in bad:
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""test_gate_decoys.py — can this gate be fooled by a LABEL? (R-421)
The controller half of the decoy sweep. Rationale, and the four failure shapes it hunts, are in
`felhom.eu/scripts/test_gate_decoys.py` and `documentation/audits/AUDIT-gate-decoys-2026-09-01.md`.
TWO HOLES THIS FILE PINS, both measured on 2026-09-01 and both fixed the same day:
* **Six gates decided their SCOPE with `os.listdir`**, one directory level. No template
subdirectory existed, so every one was green and correct — and would have stayed green the
moment anyone added `templates/partials/`, which is an ordinary act. `mojibake` and `docker-v`
already used `os.walk` and caught the same planted file, which is the control that proved the
cause was the listing and not the decoy.
* **`debug-routes` and `app-row-dedup` matched text inside COMMENTS.** A dispatcher case left in a
commented-out block counted as a live handler — which is R-400's original defect reached through
the one door its own gate could not see.
Run from `controller/`: python3 scripts/test_gate_decoys.py
Exit 0 all decoys rejected · 1 a decoy passed.
"""
import io
import os
import re
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
CTRL = os.path.dirname(HERE)
TPL = os.path.join(CTRL, "internal", "web", "templates")
SUB = os.path.join(TPL, "partials")
# ── WHAT THIS FILE COVERS ────────────────────────────────────────────────────────────────────────
# AST-parsed by felhom.eu/scripts/decoy_coverage_gate.py. See that file for why it is a declaration
# and not a grep.
COVERS = {
"emoji": "an emoji in templates/partials/ (scope was os.listdir)",
"native-confirm": "a native confirm() in templates/partials/",
"app-row-dedup": "hand-rolled row markup in partials/, AND a commented-out partial call",
"template-id": "a JS reference to a missing id, in partials/",
"secret-markup": "a secret templated into markup, in partials/",
"retrieval-promise": "an unregistered retrieval promise, in partials/",
"mojibake": "CONTROL: already walked; proves the planted file is really reachable",
"debug-routes": "a live dispatcher case commented out - the button survives, the handler dies",
"golden-notice": "R-410 in the other direction: an empty dir must not count as a bake",
}
fails = []
ran = 0
def gate(script):
p = subprocess.run([sys.executable, os.path.join("scripts", script)],
cwd=CTRL, capture_output=True, text=True)
return p.returncode, p.stdout + p.stderr
def in_subdir(name, script, content):
"""Plant a template one directory down and assert the gate still sees it."""
global ran
ran += 1
made = not os.path.isdir(SUB)
if made:
os.makedirs(SUB)
f = os.path.join(SUB, "decoy.html")
io.open(f, "w", encoding="utf-8").write(content)
try:
rc, out = gate(script)
finally:
os.remove(f)
if made and os.path.isdir(SUB) and not os.listdir(SUB):
os.rmdir(SUB)
if rc == 0:
fails.append("%s: a planted template in templates/partials/ PASSED — the gate's scope is a "
"directory listing, not the set of templates (R-421)\n%s" % (name, out[-400:]))
else:
print(" ok %-20s sees templates at any depth" % name)
def swapped(name, script, path, transform, expect="convict"):
global ran
ran += 1
b = io.open(path, encoding="utf-8").read()
try:
io.open(path, "w", encoding="utf-8").write(transform(b))
rc, out = gate(script)
finally:
io.open(path, "w", encoding="utf-8").write(b)
if (rc != 0) != (expect == "convict"):
fails.append("%s: rc=%d, expected %s\n%s" % (name, rc, expect, out[-400:]))
else:
print(" ok %-20s %s" % (name, "decoy rejected" if expect == "convict" else "genuine accepted"))
print("decoys — felhom-controller")
# --- SCOPE: the six listdir gates, each with content that actually triggers it -----------------
in_subdir("emoji", "emoji_gate.py", u"<p>Kesz \U0001F600</p>\n")
in_subdir("native-confirm", "native_confirm_gate.py",
u"<button onclick=\"confirm('biztos?')\">x</button>\n")
in_subdir("app-row-dedup", "app_row_dedup_gate.py", u'<div class="app-row ">hand-rolled</div>\n')
in_subdir("template-id", "template_id_gate.py",
u'<div id="realOne"></div>\n<script>document.getElementById("noSuchId").x=1;</script>\n')
in_subdir("secret-markup", "secret_in_markup_gate.py",
u'<input type="password" value="{{ .RetrievalPassword }}">\n')
in_subdir("retrieval-promise", "retrieval_promise_gate.py",
u"<p>A jelszavat barmikor visszaallithatja innen.</p>\n"
u"<p>Bovebben: visszaállítható a kóddal.</p>\n")
# --- CONTROL: two gates already walked. If these ever fail, the decoy is wrong, not the gate ----
in_subdir("mojibake (CONTROL)", "mojibake_gate.py", u"<p>árvíztuquotrő</p>\n")
# --- COMMENTS ARE NOT CODE (R-421) -------------------------------------------------------------
DISPATCH = os.path.join(CTRL, "internal", "web", "handler_debug.go")
DEBUG_TPL = os.path.join(CTRL, "internal", "web", "templates", "debug.html")
def _comment_out_a_real_case(src):
"""Take a LIVE dispatcher case and comment it out. The button stays; the handler dies."""
m = re.search(r'^(\s*)(case subpath == "[A-Za-z0-9/_-]+".*:)$', src, re.M)
assert m, "no dispatcher case found — the decoy cannot be built"
return src[:m.start()] + m.group(1) + "// " + m.group(2) + src[m.end():]
swapped("debug-routes/comment", "debug_route_gate.py", DISPATCH, _comment_out_a_real_case)
def _comment_out_the_partial(src):
return re.sub(r'(\{\{template "app_list_row".*?\}\})', r'<!-- was: \1 -->', src)
swapped("app-row-dedup/comment", "app_row_dedup_gate.py",
os.path.join(TPL, "dashboard.html"), _comment_out_the_partial)
# --- golden-notice: R-410's decoy, in the other direction. It is ADVISORY, so rc is never the ---
# --- question — what it COUNTED is. ---
ran += 1
EV = os.path.join(os.path.dirname(os.path.dirname(CTRL)), "felhom.eu", "documentation", "tests",
"golden-9.9.9-2026-01-01")
if os.path.isdir(os.path.dirname(EV)):
os.makedirs(EV)
try:
p = subprocess.run([sys.executable, os.path.join("scripts", "golden_notice.py"),
os.path.dirname(CTRL)], cwd=CTRL, capture_output=True, text=True)
out = p.stdout + p.stderr
finally:
os.rmdir(EV)
if "9.9.9" in out and "NOT counted" not in out:
fails.append("golden-notice: an EMPTY directory was counted as a bake (R-410 regressed)")
else:
print(" ok %-20s empty dir not counted as a bake" % "golden-notice")
else:
print(" -- %-20s SKIPPED: no felhom.eu sibling clone" % "golden-notice")
print()
if fails:
for f in fails:
print("FAIL: %s" % f)
sys.exit(1)
print("all %d controller decoys behaved — labels do not satisfy these gates" % ran)