Summary
ConfigLoader.load_config(config, use_jinja=True) renders the entire plugin
config.yaml as a Jinja2 template using the default (empty) Undefined
(cpex/framework/loader/config.py, the use_jinja branch):
jinja_env = SandboxedEnvironment(loader=jinja2.BaseLoader(), autoescape=True)
rendered_template = jinja_env.from_string(template).render(env=os.environ)
The intent is to support {{ env.X }} interpolation. But because every config
value is run through this one pass with an empty Undefined, any {{...}} that
is not an env reference is silently blanked to "" — including placeholders a
plugin legitimately stores in its config to render itself later, at runtime.
Impact (real downstream bug)
The WebhookNotification plugin stores a default_template like:
{ "event": "{{event}}", "timestamp": "{{timestamp}}", "user": "{{user}}",
"violation": {{violation}}, "metadata": {{metadata}} }
Those {{event}}/{{timestamp}}/… are meant for the plugin's own template
rendering at delivery time. After ConfigLoader.load_config, the stored template
has already been blanked to { "event": "", "timestamp": "", "violation": , ... },
so the plugin posts an all-empty (and invalid-JSON) webhook body. The
unconditionally-set timestamp being empty is the smoking gun that the blanking
happens at config load, not in the plugin.
Minimal repro
import os, jinja2
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment(loader=jinja2.BaseLoader(), autoescape=True)
print(env.from_string('{"event": "{{event}}"}').render(env=os.environ))
# -> '{"event": ""}' (the {{event}} placeholder is destroyed)
Reproduced with cpex==0.1.0; the same code is present on the default branch
(cpex/framework/loader/config.py, lines ~73-74).
Suggested fix
Don't silently destroy non-env placeholders. Options:
- Render with an undefined that preserves the original text for unknown names,
e.g. undefined=jinja2.DebugUndefined (note: it emits {{ name }} with
canonical spacing, so plugins doing exact-string substitution may need to
tolerate that), or a custom Undefined that returns the original {{name}}
verbatim.
- Restrict interpolation to the
env namespace only (e.g. a narrowly-scoped pass
that resolves {{ env.* }} and leaves all other {{...}} untouched).
- At minimum, document that plugin config values must not contain literal
{{...}}
unless they are env references, and make non-env placeholders an error rather
than silent blanking.
Current workaround
Wrap the affected config value in a Jinja raw/endraw guard in config.yaml so
the load-time pass leaves the placeholders verbatim. (Caveat: the guard tokens must
not appear in nearby YAML comments — the whole file is rendered before YAML parse,
so a guard token in a comment opens/closes the block at the wrong place.)
Summary
ConfigLoader.load_config(config, use_jinja=True)renders the entire pluginconfig.yamlas a Jinja2 template using the default (empty)Undefined(
cpex/framework/loader/config.py, theuse_jinjabranch):The intent is to support
{{ env.X }}interpolation. But because every configvalue is run through this one pass with an empty
Undefined, any{{...}}thatis not an
envreference is silently blanked to""— including placeholders aplugin legitimately stores in its config to render itself later, at runtime.
Impact (real downstream bug)
The
WebhookNotificationplugin stores adefault_templatelike:{ "event": "{{event}}", "timestamp": "{{timestamp}}", "user": "{{user}}", "violation": {{violation}}, "metadata": {{metadata}} }Those
{{event}}/{{timestamp}}/… are meant for the plugin's own templaterendering at delivery time. After
ConfigLoader.load_config, the stored templatehas already been blanked to
{ "event": "", "timestamp": "", "violation": , ... },so the plugin posts an all-empty (and invalid-JSON) webhook body. The
unconditionally-set
timestampbeing empty is the smoking gun that the blankinghappens at config load, not in the plugin.
Minimal repro
Reproduced with
cpex==0.1.0; the same code is present on the default branch(
cpex/framework/loader/config.py, lines ~73-74).Suggested fix
Don't silently destroy non-
envplaceholders. Options:e.g.
undefined=jinja2.DebugUndefined(note: it emits{{ name }}withcanonical spacing, so plugins doing exact-string substitution may need to
tolerate that), or a custom
Undefinedthat returns the original{{name}}verbatim.
envnamespace only (e.g. a narrowly-scoped passthat resolves
{{ env.* }}and leaves all other{{...}}untouched).{{...}}unless they are env references, and make non-env placeholders an error rather
than silent blanking.
Current workaround
Wrap the affected config value in a Jinja
raw/endrawguard inconfig.yamlsothe load-time pass leaves the placeholders verbatim. (Caveat: the guard tokens must
not appear in nearby YAML comments — the whole file is rendered before YAML parse,
so a guard token in a comment opens/closes the block at the wrong place.)