Skip to content

ConfigLoader.load_config Jinja pass blanks non-env {{...}} placeholders in plugin config #81

Description

@owen-pengtao

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:

  1. 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.
  2. Restrict interpolation to the env namespace only (e.g. a narrowly-scoped pass
    that resolves {{ env.* }} and leaves all other {{...}} untouched).
  3. 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.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions