fix(egress): restart dead daemons and cap inbound scan body size #466
Reference in New Issue
Block a user
Delete Branch "fix/egress-oom-455"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Fixes #455 — the egress gateway OOM that leaves every bottle on a host without egress.
Two complementary fixes:
Auto-restart dead daemons (
gateway_init.py): the supervisor already hadrestart_daemon()andrequest_restart()logic; this wires it intotick()so any unexpectedly dead daemon (e.g. an OOM-killedmitmdump) is respawned automatically without operator intervention. Implements the "eventual" failure policy described in the original module docstring.Cap inbound scan body size (
egress_addon.py): response bytes passed to the DLP inbound scan are capped atEGRESS_INBOUND_SCAN_LIMIT_BYTES(default 1 MiB). mitmproxy buffers the full response before the hook fires, but capping at scan time limits the additional amplification from decoded text and regex match strings. Bodies over the limit emit anegress_scan_truncatedlog event so the trade-off is visible. Set to0to disable.Security trade-off
As the research note documents: the scan cap is a known trade-off — content beyond the threshold is not scanned. The restart fix closes the availability gap regardless.
Test plan
test_gateway_init.py: updatedtest_child_crash_does_not_initiate_shutdown→test_child_crash_triggers_restart_not_shutdown; verifies crasher gets a new PID after deathtest_egress_addon_request_flow.py: addedTestInboundScanLimitEnv(env-var parsing) andTestInboundBodyScanCap(truncation behavior, log event, known blind-spot, cap=0 disables)Blocking:
tick()queues unexpected-death restarts for the next polling cycle, but then computesdonefrom the still-dead processes and returnsTruein the current cycle.main()consequently exits before_drain_restart_requests()runs again whenever the crashed child was the only daemon (a supportedBOT_BOTTLE_GATEWAY_DAEMONS=egressconfiguration) or all children happen to be dead. I reproduced this with a one-element_Supervisor: after the child exits, the first tick returnsTrue, retains the original PID, and leaves{'crasher'}in_restart_requested. This means the advertised auto-recovery does not work in that configuration. Please drain newly queued restarts before deciding completion, or makedonefalse while restart requests are pending, and add a single-daemon crash regression test.I could not run the full pytest selection because pytest is not installed in the review environment; the stdlib-only reproducer directly exercised the PR's
_Supervisorimplementation.Re-reviewed the updated head. The blocking lifecycle issue is fixed: deaths discovered during
tick()are restarted before completion is evaluated, so single-daemon and simultaneous all-daemon crashes no longer make the supervisor exit with a queued restart. Regression coverage now exercises both cases.Validation:
python3 -m unittest tests.unit.test_gateway_init -qpasses all 31 supervisor tests against the exact published head. Lint is green. The broader remote unit job currently reports 10 unrelatedtest_cli_start_headlessPTY-precondition errors; the changed supervisor test module passes locally and the CI failures do not touch this PR's code paths. No remaining blocking findings.Re-reviewed the updated head. The blocking restart lifecycle issue is fixed and covered by single-daemon and simultaneous-crash regression tests. All 31 gateway supervisor tests pass against the published head; lint is green. The broader remote unit failure is limited to 10 unrelated headless-CLI PTY-precondition tests. No remaining blocking findings. (Gitea will not record my approval because I contributed the fix commits, so this is submitted as a non-blocking review.)
cd56705177to2879dc34cc