From: Vsevolod Stakhov Date: Fri, 24 Apr 2026 06:43:54 +0000 (+0100) Subject: [Fix] protocol: apply inline metadata.settings on /checkv3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0faababdacc624d214039929f8f37f11802cef55;p=thirdparty%2Frspamd.git [Fix] protocol: apply inline metadata.settings on /checkv3 Inline metadata.settings on /checkv3 was stashed on task->settings directly, skipping the apply pipeline. As a result every documented "apply" effect (action thresholds, symbols / symbols_enabled / symbols_disabled, subject, variables, add/remove headers) silently no-op'd, while the same payload sent via the /checkv2 Settings HTTP header worked. Fixes #5999. Serialize the inline UCL object to compact JSON and synthesize a Settings request header so the existing settings.lua check_query_settings -> apply_settings -> {task:set_settings, apply_settings_side_effects} pipeline runs uniformly with v2. One source of truth, no duplicated apply logic in C. Also update the "settings will be merged" log to "inline settings will take precedence" since that is what settings.lua actually does when both settings_id and inline settings are present. Audit-companion fix: metadata.deliver_to now goes through rspamd_protocol_escape_braces too, matching the v2 Deliver-To header which has stripped <...> braces since the beginning. Add two regression tests in 430_checkv3.robot covering the Lua-side side effect path (settings.symbols injection) and the C-side action threshold path (actions.reject override). --- diff --git a/src/libserver/protocol.c b/src/libserver/protocol.c index 57c6b850a8..b795d9a2f6 100644 --- a/src/libserver/protocol.c +++ b/src/libserver/protocol.c @@ -2429,7 +2429,14 @@ rspamd_protocol_handle_metadata(struct rspamd_task *task, /* deliver_to */ elt = ucl_object_lookup(metadata, "deliver_to"); if (elt && ucl_object_type(elt) == UCL_STRING) { - task->deliver_to = rspamd_mempool_strdup(task->task_pool, ucl_object_tostring(elt)); + size_t deliver_len; + const char *deliver_str = ucl_object_tolstring(elt, &deliver_len); + + if (deliver_len > 0) { + rspamd_ftok_t deliver_tok = {.begin = deliver_str, .len = deliver_len}; + /* Match v2 Deliver-To header semantics: strip <...> braces */ + task->deliver_to = rspamd_protocol_escape_braces(task, &deliver_tok); + } } /* settings_id */ @@ -2444,9 +2451,40 @@ rspamd_protocol_handle_metadata(struct rspamd_task *task, if (elt && ucl_object_type(elt) == UCL_OBJECT) { if (task->settings_elt) { msg_info_protocol("both settings_id and inline settings present, " - "settings will be merged"); + "inline settings will take precedence"); + } + + /* + * Serialize the inline UCL settings to JSON and synthesize a 'settings' + * request header so the existing settings.lua check_query_settings + * pipeline picks them up and runs the apply path uniformly with the v2 + * Settings HTTP header. Without this, action thresholds, symbols + * enable/disable lists, subject rewriting, etc. would never take + * effect on /checkv3. + */ + size_t json_len = 0; + unsigned char *json = ucl_object_emit_len(elt, UCL_EMIT_JSON_COMPACT, + &json_len); + + if (json && json_len > 0) { + char *val_dup = rspamd_mempool_alloc(task->task_pool, json_len); + memcpy(val_dup, json, json_len); + + rspamd_ftok_t *name_tok = rspamd_mempool_alloc(task->task_pool, + sizeof(*name_tok)); + rspamd_ftok_t *val_tok = rspamd_mempool_alloc(task->task_pool, + sizeof(*val_tok)); + + RSPAMD_FTOK_ASSIGN(name_tok, SETTINGS_HEADER); + val_tok->begin = val_dup; + val_tok->len = json_len; + + rspamd_task_add_request_header(task, name_tok, val_tok); + } + + if (json) { + free(json); } - task->settings = ucl_object_ref(elt); } /* tls.cipher - sets SSL flag */ diff --git a/test/functional/cases/001_merged/430_checkv3.robot b/test/functional/cases/001_merged/430_checkv3.robot index 92c66a3f62..45429b88a2 100644 --- a/test/functional/cases/001_merged/430_checkv3.robot +++ b/test/functional/cases/001_merged/430_checkv3.robot @@ -27,6 +27,22 @@ checkv3 with settings_id Scan File V3 ${GTUBE} metadata=${meta} Expect Symbol GTUBE +checkv3 inline metadata.settings injects symbol + [Documentation] Inline metadata.settings must run apply_settings_side_effects + ... so settings.symbols actually fires (issue #5999) + ${settings_obj} = Evaluate {"symbols": {"INLINE_V3_TEST": 1.0}} + &{meta} = Create Dictionary settings=${settings_obj} + Scan File V3 ${GTUBE} metadata=${meta} + Expect Symbol INLINE_V3_TEST + +checkv3 inline metadata.settings overrides reject threshold + [Documentation] Inline metadata.settings.actions must invoke the C-side + ... action threshold apply path (issue #5999) + ${settings_obj} = Evaluate {"actions": {"reject": 1.0}} + &{meta} = Create Dictionary settings=${settings_obj} + Scan File V3 ${GTUBE} metadata=${meta} + Expect Action reject + checkv3 missing metadata part [Documentation] Send only message part without metadata, expect HTTP 500 (400 error mapped to 5xx) ${status} = Scan File V3 Single Part message test message body