Files
felhom.eu/documentation/audits/new-app-checklist-2026-10-01/tools/c_wger2.py
T
admin 2d69c61556
gates / gates (push) Successful in 27s
New-app checklist: the pilot audit (wger as of 2026-09-29 and now, two 9202 walks), R-758..R-764 opened; STATUS, CONTEXT, report
The operator's request of 2026-10-01 recorded in CONTEXT with the reviewer's defaults (new apps only; the 53 get a
read-only gap page). The pilot: the draft caught R-752 and R-755, missed R-737 and (for a new app) R-738; the
sharpened and new rows then found R-762 (wger serves no static files or photos), R-763 (strangers sign up, guest
accounts), R-764 (no mail). Also R-758 (8 mem_limit under the sum), R-759 (wger's open record rows), R-760
(vikunja healthcheck), R-761 (logo comment). R-755 note. Register 392 -> 399.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-01 15:22:05 +02:00

143 lines
8.3 KiB
Python

#!/usr/bin/env python3
"""Part C, second short walk on 9202 (drill catalog): the three things the first walk's reads raised.
3.4 wger's settings read ALLOW_REGISTRATION=True and ALLOW_GUEST_USERS=True (C2): can a STRANGER make an account
after the household's admin exists — by sign-up, and by "guest"? Through traefik, no dashboard session.
2.8 a photo uploaded through the API answered 201 and then 404 (C4): is the file on the volume, and who would
serve /media/ — the cause, read inside the container.
1.7 the entrypoint runs collectstatic only when DJANGO_DEBUG == "False", and the template sets no DJANGO_DEBUG:
does a static file answer?
Then remove through the product. Secrets never printed.
"""
import io, json, os, re, sys, tempfile, time, secrets
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
import box_walk as w
import upgrade_fixtures_box as fxb
EV = os.environ["EV"]
SUB = "fitness"
OUT = []
def p(*a):
line = " ".join(str(x) for x in a)
print(line, flush=True)
OUT.append(line)
def dump(name):
with io.open(os.path.join(EV, name), "w", encoding="utf-8") as fh:
fh.write("\n".join(OUT) + "\n")
w.login()
p("##### wger second walk — controller", w.guest("docker inspect felhom-controller --format {{.Config.Image}}").strip(),
"catalog", w.guest("cd /var/lib/docker/volumes/felhom-controller-data/_data/data/catalog-cache && git log --oneline -1").strip())
p("deploy ->", w.deploy("wger", SUB))
for _ in range(120):
time.sleep(5)
if "hold OPENED" in w.guest("docker logs --since 20m felhom-controller 2>&1 | grep 'wger: install hold OPENED'"):
break
w.wait_app(SUB, "/en/user/login", want=("200",), tries=72)
pw = (w.GENERATED.get("wger") or {}).get("ADMIN_PASSWORD") or ""
W = fxb.Wger()
jar = tempfile.mktemp(prefix="wger-jar-")
hdr, why = W._login(w, SUB, pw, jar)
p("household admin signs in (the admin exists, the setup is done):", why)
# ---- 3.4 a stranger signs up
p("\n== 3.4 a STRANGER, after the setup — no dashboard session, no gate cookie, through traefik")
p("wger source: where registration and guests are decided:")
p(w.guest("docker exec wger sh -c \"grep -rn -E 'ALLOW_REGISTRATION|ALLOW_GUEST_USERS' /home/wger/src/wger --include=*.py | grep -v -E 'tests?/' | head -12\"").rstrip())
sj = tempfile.mktemp(prefix="wger-stranger-")
o = f"https://{SUB}.{w.DOMAIN}"
sh_ = ["-H", f"Origin: {o}", "-H", f"Referer: {o}/en/user/registration", "-c", sj, "-b", sj]
rc, code, page = w.app_curl(SUB, "/en/user/registration", *sh_)
fields = sorted(set(re.findall(r'name="([a-z_0-9]+)"', page or "")))
p("GET /en/user/registration ->", code, "form fields:", fields)
m = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', page or "")
uname = "stranger" + secrets.token_hex(3)
spw = "Str-" + secrets.token_hex(8)
if m:
data = ["--data-urlencode", f"csrfmiddlewaretoken={m.group(1)}", "--data-urlencode", f"username={uname}",
"--data-urlencode", f"email={uname}@example.invalid", "--data-urlencode", f"password1={spw}",
"--data-urlencode", f"password2={spw}"]
rc, code, body = w.app_curl(SUB, "/en/user/registration", *sh_, *data, method="POST")
errs = re.findall(r'class="[^"]*(?:invalid-feedback|errorlist|alert-danger)[^"]*"[^>]*>\s*([^<]{3,120})', body or "")
p(f"POST /en/user/registration as {uname} -> {code}; form errors: {errs[:3]}")
sj2 = tempfile.mktemp(prefix="wger-stranger2-")
sh2 = ["-H", f"Origin: {o}", "-H", f"Referer: {o}/en/user/login", "-c", sj2, "-b", sj2]
rc, code, pg = w.app_curl(SUB, "/en/user/login", *sh2)
m2 = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', pg or "")
rc, code, _ = w.app_curl(SUB, "/en/user/login", *sh2, "--data-urlencode", f"csrfmiddlewaretoken={m2.group(1) if m2 else ''}",
"--data-urlencode", f"login={uname}", "--data-urlencode", f"password={spw}", method="POST")
p(f"the stranger then signs in with that account -> {code} ({'302 = IN' if code == '302' else 'refused'})")
rc, code, out = w.app_curl(SUB, "/api/v2/weightentry/", *sh2)
p(" and reads the API as that user ->", code)
for f in (sj2,):
os.path.exists(f) and os.unlink(f)
p("the app's own user list now (admin's view, count only):", w.guest(
"docker exec wger sh -c \"cd /home/wger/src && python3 manage.py shell -c 'from django.contrib.auth.models import User; print(User.objects.count(), sorted(u.username[:8] for u in User.objects.all()))'\" 2>/dev/null | tail -1").strip())
p("\n-- guests: wger's own 'try as guest' path, as a stranger")
p(w.guest("docker exec wger sh -c \"grep -rn -E 'create_temporary_user|def demo_entries|guest' /home/wger/src/wger/core/urls.py /home/wger/src/wger/core/views/user.py /home/wger/src/wger/utils/*.py 2>/dev/null | head -12\"").rstrip())
gj = tempfile.mktemp(prefix="wger-guest-")
gh = ["-c", gj, "-b", gj]
for path in ("/en/user/demo-entries", "/en/dashboard"):
rc, code, body = w.app_curl(SUB, path, *gh)
p(f"GET {path} as a stranger -> {code}")
p("users after the guest tries:", w.guest(
"docker exec wger sh -c \"cd /home/wger/src && python3 manage.py shell -c 'from django.contrib.auth.models import User; print(User.objects.count(), sorted(u.username[:8] for u in User.objects.all()))'\" 2>/dev/null | tail -1").strip())
os.path.exists(gj) and os.unlink(gj)
os.path.exists(sj) and os.unlink(sj)
# ---- 2.8 the photo
p("\n== 2.8 the uploaded photo: on the volume? who serves /media/?")
import base64
png = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")
fn = tempfile.mktemp(suffix=".png")
open(fn, "wb").write(png)
rc, code, out = w.app_curl(SUB, "/api/v2/gallery/", *hdr, "-F", f"image=@{fn};type=image/png", "-F", "date=2026-10-01",
"-F", "description=felhom pilot 2", method="POST")
url = ""
try:
url = json.loads(out).get("image") or ""
except Exception:
pass
path = re.sub(r"^https?://[^/]+", "", url)
p("POST /api/v2/gallery/ ->", code, "image path:", path)
p("the file on the media volume:", w.guest(f"docker exec wger ls -la /home/wger/media{path.replace('/media', '', 1)} 2>&1").strip())
rc, code, _ = w.app_curl(SUB, path, *hdr)
p("GET it through traefik, signed in ->", code)
p("inside the container, straight at runserver (no traefik):", w.guest(f"docker exec wger sh -c \"python3 -c \\\"import urllib.request as u;\ntry:\n print(u.urlopen('http://127.0.0.1:8000{path}').status)\nexcept Exception as e: print(e)\\\"\"").strip())
p("wger's urls.py on /media and /static:", w.guest("docker exec wger sh -c \"grep -n -E 'MEDIA|static\\(|serve' /home/wger/src/wger/urls.py | head -8\"").rstrip())
p("whitenoise / static middleware in settings:", w.guest("docker exec wger sh -c \"grep -n -i -E 'whitenoise|STATIC_ROOT|MEDIA_ROOT' /home/wger/src/settings/*.py /home/wger/src/wger/settings_global.py 2>/dev/null | head -8\"").rstrip())
p("upstream's own production compose serves /media with nginx — the image's docs:", w.guest("docker exec wger sh -c \"ls /home/wger/src/extras/docker/production 2>/dev/null; grep -rn -l 'location /media' /home/wger/src/extras 2>/dev/null | head -3\"").strip() or "(no extras/docker/production in the image)")
os.unlink(fn)
# ---- 1.7 a static file
p("\n== 1.7 static files (collectstatic needs DJANGO_DEBUG == \"False\"; the template sets none)")
rc, code, body = w.app_curl(SUB, "/en/user/login")
css = re.findall(r'(?:href|src)="(/static/[^"]+\.(?:css|js))"', body or "")
p("static refs on the login page:", css[:3])
for c in css[:2]:
rc, code, b = w.app_curl(SUB, c)
p(f"GET {c} -> {code} ({len(b)} chars)")
p("static root inside the container:", w.guest("docker exec wger sh -c 'ls /home/wger/static 2>&1 | head -5; du -sh /home/wger/static 2>/dev/null'").strip())
dump("C8-signup-guest-media-static.txt")
# ---- remove
OUT.clear()
c1, _ = w.ctl("POST", "/api/stacks/wger/stop")
for _ in range(24):
time.sleep(5)
if w.stack("wger").get("state") != "running":
break
code, d = w.ctl("POST", "/api/stacks/wger/remove", {"remove_hdd_data": True, "remove_backups": True})
p("remove WITH data ->", code, str(d)[:200])
time.sleep(6)
p("left after: stack dir / containers / volumes:", w.guest(
"ls -d /opt/docker/stacks/wger 2>/dev/null; docker ps -a --format '{{.Names}}' | grep -x wger; docker volume ls -q | grep -i wger").strip() or "nothing")
dump("C8b-remove-with-data.txt")
os.path.exists(jar) and os.unlink(jar)