v0.129.0: CAMPAIGN-4 fixes — rate-limiter key (F-B) + volume-blind estimate (F-A) + no-op claim status (F-C)

F-B (MED, security): shared clientIP(r) helper (XFF first-hop, else SplitHostPort
host, else raw) replaces requestIP + the duplicated inline derivation in handleLogin,
so login AND escrow re-auth key on the port-stripped host IP — distinct direct
connections no longer evade the failed-attempt counter. XFF-trust out of scope (commented).

F-A (MED, honesty): volumeSizer seam reads volume size from a container view
(docker run --rm -v vol:/vol:ro alpine du -sb /vol), replacing the host-path du that
returned 0 inside the containerized controller. Failed read -> size_unknown +
fits_on_dest forced false (never "fits"). Export pre-flight hard-aborts only on a
KNOWN doesn.t-fit. HDD branch unchanged.

F-C (LOW-MED): escrowClaimAPIHandler relays agent 404 -> clean 404 and 409 -> 409;
410 and genuine-unreachable 502 unchanged (was: 404 fell through to 502).

Tests + red-proofs: ratelimit_ip_test.go (F-B x6), estimate_volsize_test.go (F-A x3),
TestEscrowClaim_ProxySemantics +3 (F-C). Alpine busybox du -sb verified prod-valid.

Claude-Session: https://claude.ai/code/session_01LbMm4T7Ayzs1unB9pN6Uqd
@
This commit is contained in:
2026-07-14 09:52:11 +02:00
parent 3c9de42c20
commit 7465713a2f
10 changed files with 414 additions and 35 deletions
+36
View File
@@ -1,5 +1,41 @@
## Changelog
### v0.129.0 — CAMPAIGN-4 fixes: rate-limiter key (F-B) + volume-blind estimate (F-A) + no-op claim status (F-C) (2026-07-14)
Three controller-side fixes from CAMPAIGN-4 (2026-07-13). Controller-only.
- **F-B (MED, security):** the login/escrow-reauth rate-limiter keyed on `r.RemoteAddr` (which is
`IP:PORT`) whenever `X-Forwarded-For` was absent, so every fresh direct connection from one host
got a distinct ephemeral port → a distinct key → the failed-attempt counter never accrued. A
direct-to-controller path (LAN/guest, bypassing the traefik/CF proxy) therefore had **no
brute-force protection**. Fix: a single shared `clientIP(r)` helper (XFF first-hop, else
`net.SplitHostPort(RemoteAddr)` host, else raw) — replaces the former `requestIP` and the
duplicated inline derivation in `handleLogin`, so the escrow re-auth limiter shares the exact same
fixed key. Accepted limitation (out of scope, commented): XFF is attacker-controlled on a direct
path — the fix closes the port-in-key bug, not XFF trust. Red-proof: revert `clientIP` to raw
`RemoteAddr` → the distinct-ports scenario stops limiting and escrow re-auth stays 401 not 429.
- **F-A (MED, honesty):** the export size-estimate's volume branch `du`'d the raw host mountpoint
from `docker volume inspect`, which is not mounted inside the containerized controller → returned
0, so a >1 GB volume-only app reported `data_size_bytes:0` / "3.6 KB" / `fits_on_dest:true`. Fix:
a `volumeSizer` seam whose real impl reads the size from a **container view** (`docker run --rm -v
<vol>:/vol:ro alpine du -sb /vol` — the same named-volume pattern the export path uses; never a
controller-host path, the v0.125.0 strand class). A failed read now sets `size_unknown` and
**forces `fits_on_dest:false`** (never renders as "fits") with the human string "ismeretlen méret".
The export pre-flight hard-aborts only on a KNOWN doesn't-fit (an unmeasured size no longer blocks
the export — the tar stream + destination FS surface a real ENOSPC). HDD-path branch unchanged.
Red-proof: revert the estimate to the host-path read → the >1 GiB scenario reads 0.
- **F-C (LOW-MED, correctness):** a no-op escrow claim (agent `phase:none` → HTTP 404) fell through
`escrowClaimAPIHandler` to a generic **502**. Fix: relay the agent's 404 as a clean 404 ("Nincs
aktív helyreállítási folyamat…") and 409 as 409; 410 (void) and a genuinely-unreachable agent
(status 0 → real bad gateway) are unchanged. Red-proof: remove the 404 mapping → the no-ceremony
claim returns 502.
Tests (non-hollow, all red-proofed): `ratelimit_ip_test.go` (F-B: 6 — direct-distinct-ports,
stable-XFF, rotating-XFF, escrow-reauth-shared-key, success-clears, clientIP unit),
`estimate_volsize_test.go` (F-A: 3 — real-not-zero, failure-never-fits, HDD-unchanged),
`TestEscrowClaim_ProxySemantics` +3 subtests (F-C: 404-not-502, 409, unreachable-stays-502). Live:
Alpine busybox `du -sb` verified supported (prod-valid).
### v0.128.1 — USB drives never show the rotational class hint (2026-07-13, ruling F5)
`storage.html` `classTag(d)`: `if(d.type==='usb') return '';` ahead of the class branches —