Found reviewing PR #12895 (merged, fa71bc9d2) after the fact. The PR is otherwise sound — the report path is correctly ungated, the sysctl write and the core directory are correctly gated behind backend_core_capture: false. The resource limit that actually permits core writing is not gated at all.
The gap
roles/backend/templates/autobot-backend.service.j2:46:
LimitCORE={{ backend_core_limit | default('infinity') }}
There is no {% if backend_core_capture %} around it, so every node that renders this unit gets LimitCORE=infinity, including the default backend_core_capture: false case.
The inline comment argues this is harmless:
Harmless while capture is off: the kernel still has nowhere to put the core.
That holds only on a node whose core_pattern pipes to a missing handler — which is the specific broken state of the node in #12777, not a general property. On any node with working core handling (systemd-coredump is the distro default on Ubuntu/Debian/Fedora), the unit changes from never writes a core to writes a full process memory image on every abort.
Why that matters
The backend holds decrypted secrets in memory — AUTOBOT_JWT_SECRET, DB credentials, provider API keys, Redis credentials. A core dump is a verbatim image of that memory, written to a world-of-root-readable location, retained by the coredump handler's own policy, and not covered by any of the repo's secret-redaction paths.
Second-order: the backend is a large process and #12777 documents it aborting every ~25-55 minutes. On a node with working capture that is an unbounded disk-fill vector.
Both are latent — they need a node whose core handling works — which is exactly why this will not show up on the #12777 node it was tested against.
Second, smaller defect
backend_core_limit is not declared in roles/backend/defaults/main.yml. The same PR correctly declared backend_core_capture and backend_core_dir there; this one relies solely on the inline Jinja default('infinity'). An undeclared role variable is invisible to anyone reading defaults/, which is where the repo documents every other tunable in this role.
Scope
- Gate
LimitCORE on backend_core_capture, or default it to 0 so the limit follows the opt-in rather than leading it.
- Declare
backend_core_limit in defaults/main.yml with the same recorded-decision comment style used for backend_core_capture.
- Extend
tests/test_backend_service_faulthandler_12777.py: it currently asserts LimitCORE= is present and that backend_core_limit="0" renders LimitCORE=0, but nothing asserts the default-off case — add one pinning the default when backend_core_capture is false.
Done when
- A stock node with
backend_core_capture: false and a working core_pattern writes no core.
backend_core_limit appears in defaults/main.yml.
- A test pins the default-off behaviour.
Refs #12777, #12897, #12895
Found reviewing PR #12895 (merged,
fa71bc9d2) after the fact. The PR is otherwise sound — the report path is correctly ungated, thesysctlwrite and the core directory are correctly gated behindbackend_core_capture: false. The resource limit that actually permits core writing is not gated at all.The gap
roles/backend/templates/autobot-backend.service.j2:46:LimitCORE={{ backend_core_limit | default('infinity') }}There is no
{% if backend_core_capture %}around it, so every node that renders this unit getsLimitCORE=infinity, including the defaultbackend_core_capture: falsecase.The inline comment argues this is harmless:
That holds only on a node whose
core_patternpipes to a missing handler — which is the specific broken state of the node in #12777, not a general property. On any node with working core handling (systemd-coredumpis the distro default on Ubuntu/Debian/Fedora), the unit changes from never writes a core to writes a full process memory image on every abort.Why that matters
The backend holds decrypted secrets in memory —
AUTOBOT_JWT_SECRET, DB credentials, provider API keys, Redis credentials. A core dump is a verbatim image of that memory, written to a world-of-root-readable location, retained by the coredump handler's own policy, and not covered by any of the repo's secret-redaction paths.Second-order: the backend is a large process and #12777 documents it aborting every ~25-55 minutes. On a node with working capture that is an unbounded disk-fill vector.
Both are latent — they need a node whose core handling works — which is exactly why this will not show up on the #12777 node it was tested against.
Second, smaller defect
backend_core_limitis not declared inroles/backend/defaults/main.yml. The same PR correctly declaredbackend_core_captureandbackend_core_dirthere; this one relies solely on the inline Jinjadefault('infinity'). An undeclared role variable is invisible to anyone readingdefaults/, which is where the repo documents every other tunable in this role.Scope
LimitCOREonbackend_core_capture, or default it to0so the limit follows the opt-in rather than leading it.backend_core_limitindefaults/main.ymlwith the same recorded-decision comment style used forbackend_core_capture.tests/test_backend_service_faulthandler_12777.py: it currently assertsLimitCORE=is present and thatbackend_core_limit="0"rendersLimitCORE=0, but nothing asserts the default-off case — add one pinning the default whenbackend_core_captureis false.Done when
backend_core_capture: falseand a workingcore_patternwrites no core.backend_core_limitappears indefaults/main.yml.Refs #12777, #12897, #12895