Skip to content

fix: wave 5 — base path, backup, stats, assets/CSP, login throttle, cleanup - #92

Merged
coderbuzz merged 10 commits into
mainfrom
fix/wave5-misc
Oct 15, 2026
Merged

coderbuzz merged 10 commits into
mainfrom
fix/wave5-misc

Conversation

@coderbuzz

Copy link
Copy Markdown
Owner

Wave 5 of the Fable 5.1 audit (audits/DOCKIFY-AUDIT-FABLE-5.1.md), one commit per item.

Item Change Test
B8 Base-path helpers move to internal/web (SetBasePath, GetBasePath, Redirect); every redirect in app/server handlers, auth (middleware, login, logout) and the /login route goes through web.Redirect. The last absolute template links (/export, /import, /apps/…, two form actions) are now relative to <base href>. TestRedirectKeepsBasePath (real PrefixMiddleware + login POST, with and without X-Forwarded-Prefix)
B9 Export adds memory/cpu/log limits, command, ports, ulimits_nofile and domains (primary first); import restores them and saves one route per domain (old exports fall back to domain). Replace mode is refused up front with "stop or undeploy running apps before a replace import" unless every app is draft/created/stopped; undeploy/delete errors now stop the import. TestExportImportRoundTrip (two temp DBs, mock SSH, encrypted export, two domains, replace refusal leaves data untouched)
B14 The live docker stats stream restarts with a 1 s backoff until the WebSocket closes. none (goroutine in the WS handler)
B15 Samples always overwrite the live fields; the disk collector now writes only the disk field (updateDiskCache, replaces the single-app UpdateStatsCache). TestStatsCacheAcceptsIdleSample
B16 DELETE /api/apps/{id}/files/* — chi regexp params never span /, so {path:.*} does not match conf/app.ini; the wildcard does. TestDeleteNestedFilePath (real router)
B19 Render executes into a buffer, then writes status + body; errors give a clean 500. TestRenderExecErrorIs500
B20, S12 htmx 2.0.4, xterm 5.3.0, xterm-addon-fit 0.8.0 vendored in internal/http/static/ (URLs + sha256 in SOURCES.md), embedded and served at /static/* (one-week cache, no listing); dead web/static route removed. Every response sends the CSP from the brief, X-Content-Type-Options: nosniff, Referrer-Policy: same-origin. TestStaticAssetsAndCSP
S9 Constant-time user and password compare, 1 s delay on failure, >10 failures / 15 min per client IP → 429 for the rest of the window, expired sessions pruned on login. chimw.RealIP only with the new DOCKIFY_TRUST_PROXY=true. TestLoginThrottle
A4 Deleted unused FindByGitRepo, backup.encrypt, DeleteDNSRecord, LatestContainerStats, server LatestStats, nullFloat64/nullInt64/nullString (server), cloudflare UpsertRecord, Config.AuthEnabled. One scanApp/scanServer per repository; stats_ws.go uses the exported server.Parse* metric parsers. No behaviour change. existing tests
A6 Server list cache removed; List() queries the DB. TestListShowsStatusChange

README and SPEC updated (base path, backup fields and replace rule, self-hosted assets + CSP, login throttle, DOCKIFY_TRUST_PROXY, nested file route).

Operator note (S9): production runs behind Caddy on the same host, so without DOCKIFY_TRUST_PROXY=true every client shares the counter of 127.0.0.1, and 10 bad logins from anyone lock the UI for 15 minutes. Set DOCKIFY_TRUST_PROXY=true there (and bind 8080 to 127.0.0.1, as §0.1 already recommends).

Verification

  • go build ./... && go vet ./... && gofmt -l . && go test -race -timeout 300s ./...: green; scripts/smoke-test.sh: passed.
  • Mock mode (DOCKIFY_DEV_MOCK=true, 127.0.0.1): 10 bad logins → 401, 11th → 429, correct password inside the window → 429, spoofed X-Forwarded-For → still 429; pages carry the CSP and load static/*.js; /static/* files served with matching sha256, /static/ → 404; POST /apps/4/stop with X-Forwarded-Prefix: /proxy/9898 → Location: /proxy/9898/apps/4 (without the header: /apps/4); export → replace import refused while apps ran → stop → replace import restored limits, command, ports, ulimits and both routes.
  • Browser (desktop app's browser pane, mock mode): htmx 2.0.4, xterm and FitAddon load from /static/, no console errors, app console opens, live stats and htmx stats cards update, server charts draw, Google Fonts load, an external fetch is blocked by connect-src.

Not verified

  • B14 against a real worker (container restart while the live view is open); mock mode streams via polling, so the restart loop only ran against code review.
  • Base-path mode through a real reverse proxy (checked with the header only).
  • Replace import against real workers (mock SSH only).

🤖 Generated with Claude Code

coderbuzz and others added 10 commits October 15, 2026 14:42
Move SetBasePath/GetBasePath into internal/web with a Redirect helper that
prepends the base path, use it for every redirect in the app/server handlers
and auth, and make the remaining absolute template links relative to <base href>.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…un (B9)

Export memory/cpu/log limits, command, ports, ulimits and every routed domain;
import restores them with one route per domain. A replace import is refused
up front unless every app is draft, created or stopped, and removal errors
now stop the import instead of being ignored.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Samples now always overwrite the live fields; the disk collector updates
only the disk field through updateDiskCache, which replaces UpdateStatsCache.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
chi regexp params never span a slash, so {path:.*} would not help; the route
uses the * wildcard and the handler reads it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vendor htmx 2.0.4, xterm 5.3.0 and xterm-addon-fit 0.8.0 into
internal/http/static (sources and sha256 in SOURCES.md), embed and serve them
at /static/* without directory listing, replacing the dead web/static route.
Every response carries Content-Security-Policy, X-Content-Type-Options and
Referrer-Policy.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…le (S9)

Failed logins wait 1 s; more than 10 failures from one client IP within
15 minutes answer 429 for the rest of the window. chimw.RealIP now applies
only with DOCKIFY_TRUST_PROXY=true so a spoofed X-Forwarded-For cannot dodge
or aim the lockout. Expired sessions are pruned on each login.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Remove FindByGitRepo (selected 19 columns, scanned 21), backup.encrypt,
DeleteDNSRecord, LatestContainerStats, server LatestStats and the
nullFloat64/nullInt64/nullString helpers, cloudflare UpsertRecord and
Config.AuthEnabled, all unused. One scanApp/scanServer per repository for
every SELECT of the full row, and the live stats WebSocket uses the server
package's exported metric parsers instead of its own copies. No behaviour
change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@coderbuzz
coderbuzz merged commit 1d79787 into main Oct 15, 2026
2 checks passed
@coderbuzz
coderbuzz deleted the fix/wave5-misc branch October 15, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant