Wires cauldron up to the unified Sulkta bug tracker per
memory/spec-bugs-unified-sdk.md (Phases 1-7 shipped 2026-05-02).
What's included:
- Vendored bugs-sulkta-py at cauldron/vendor/bugs_sulkta (4 stdlib-only
files copied verbatim from Sulkta-Coop/bugs-sulkta-py main). Same
vendoring approach as TC's backend/api/bugs_sulkta — Docker BuildKit
can't reach LAN Gitea, so the package ships in the source tree.
- BUGS_API_KEY + BUGS_BASE_URL env (config.py). Empty key = page renders
"not configured" placeholder; POSTs return 503. Lets dev runs skip
provisioning a key.
- New routes (server.py): GET /bugs (page), GET /api/bugs (list),
POST /api/bugs (create). All session-auth. Per-service key returns
every cauldron report; we filter client-side by user_email so each
household member sees only their own. Admins get a "show all" toggle.
- bugs.html template in mythic-witch style: subject + message + kind
+ severity form, filed-reports list with status glyphs (📂 open
🔨 in-progress ✅ resolved ⛔ wontfix), relative timestamps.
- _base.html nav: 🐞 bugs link between discover and me.
- Server-side auto-fill: user_email/user_name from session, page_url
from referrer, user_agent from request headers.
Defaults are dev-safe — no env change required for the LAN soak. When
Cobb mints the key with:
docker exec bugs-sulkta bugs-sulkta-cli keys create \
--service=cauldron --scopes=read,write,update \
--description="cauldron prod"
…drop it into BUGS_API_KEY and the page lights up.
Cobb requested all the small ones land before LAN testing.
discover.html CVE-NEW3-2 (LOW): switched recipe card image from a CSS
background-image:url('${_esc(url)}') to a plain <img class="img" src=...>
element. Recipe image_url is scraped from JSON-LD on third-party pages —
a malicious page could return an image_url crafted to close the CSS
url(...) string and inject layout-breaking CSS. With <img src=...> the
URL stays in HTML-attribute context end-to-end where _esc is sufficient.
Also adds defense-in-depth: validate URL parses as http(s) before
rendering, fall through to placeholder otherwise, and set
referrerpolicy=no-referrer so we don't leak our path to image hosts.
CSS for .dcard .img widened with object-fit:cover so img and div both
center/cover correctly.
server.py CVE-NEW3-3 (LOW): _origin_of() now lowercases scheme AND
host (urlparse only does scheme), and drops scheme-default ports
(:80/:443) so `https://x.com:443` matches `https://x.com`. Closes a
false-reject path on browsers that preserve case in Origin headers,
or non-canonical CAULDRON_BASE_URL values. Not a bypass — false-reject
robustness only — but cheap to fix and operationally important.
server.py CODE3-3 (LOW): _safe_next() now allows `%` in the path
charset so percent-encoded paths (e.g. /recipes/spaghetti%20bol)
don't silently land at /me. Defense-in-depth: also percent-decode
the path and reject if the decoded form contains `..` traversal or
`//` (encoded forms of the same patterns the front-of-function
reject already).
server.py INFO3-2: auth_callback now does session.clear() before
setting session["user"]. Capture+revalidate `next` BEFORE the clear
so we don't drop our own redirect target. Drops every pre-auth key
on login — defense-in-depth against session-state contamination if
anything else ever lands in pre-auth session.
.env.example INFO3-1: added CAULDRON_ADMIN_SUBS, CAULDRON_BASE_URL,
CAULDRON_BEHIND_TLS, CAULDRON_TRUSTED_PROXIES with comments
explaining what each one gates. Defaults are the safe-LAN set.
Holding for public deploy — Cobb running LAN tests for a few days.
INFO3-3 (rate limit) intentionally NOT addressed in code: the audit
notes this as architecturally a proxy-layer concern (rackham vhost),
not in-process. Rolled into the public-deploy commit when the vhost
work lands.
INFO3-4 (security primitive test coverage) deferred — separate test-
sweep PR, doesn't block deploy.
LAN-only Flask API that consumes Mealie (source of truth for recipes / plans
/ lists) and clawdforge (centralized claude -p runner) to do AI work.
v0.1 surface:
GET /healthz liveness + clawdforge upstream
GET /api/recipes proxy Mealie recipe list
POST /api/sterilize/preview/<slug> dry-run AI parse, return proposals
POST /api/sterilize/apply/<slug> write parses back to Mealie
Why sterilizer first: Mealie's CRF parser is mediocre and Cobb's hand-typed
recipes have lots of free-form ingredient strings ("about 2 cups cooked
white rice", "a pinch of salt") that don't aggregate cleanly into a
shopping list. We batch all ingredients of one recipe into a single Sonnet
call via clawdforge, get back parallel structured parses, then on apply
link each to Mealie food/unit records (creating missing by name) and PUT
the recipe back. Preview is non-destructive.
No UI in v0.1 — bearer-auth API only. Frontend + Authentik OIDC + Abby's
swamp/meadow/forest palette arrives in v0.2.
Auth: simple shared bearer in env (ADMIN_BEARER) until OIDC lands. LAN-only
deploy means the bearer is the only gate; no public exposure.
Stack: python:3.12-slim + Flask 3 + gunicorn + requests. No DB in v0.1.