Add a json template helper for webhook bodies - #127
Merged
Conversation
text/template does no escaping of its own, so a custom webhook template
whose body is JSON (e.g. `{"text": "{{.Message}}"}`) inserted a resource
string raw. A disk alert's .Resource is a mountpoint read from
/proc/mounts, which can contain a quote or backslash, producing a
malformed request body.
Register a `json` function on the template that marshals its argument
and supplies its own surrounding quotes, so authors write
`{{json .Message}}` instead of quoting the interpolation by hand.
Existing templates that don't use `json` are unaffected.
Update the shipped example config and the architecture doc to
recommend the safe form.
LarsLaskowski
commented
Aug 23, 2026
Owner
Author
There was a problem hiding this comment.
Reviewed against the repo's Go/security/API conventions. go build ./..., go vet ./... and go test ./... -race -cover all pass on f636e26 (internal/alert at 92.8%).
One finding, inline on packaging/pimonitor.example.yaml. Everything else on the checklist is clean: no new dependencies, no /api/v1/... shape change, no exec.Command or /proc//sys surface touched, privilege split unaffected, and a template render error is already handled gracefully (logged and the event dropped in dispatch) rather than panicking.
The example dropped the old "PiMonitor: " prefix, and neither it nor
the ARCHITECTURE.md paragraph showed how to combine literal text with
an escaped value. Someone restoring the prefix as
"PiMonitor: {{json .Message}}" would get invalid JSON, since json
supplies its own quotes and they nest inside the literal ones —
reproducing exactly the bug this PR fixes.
Show the printf composition form instead, which wraps the whole string
value in json and round-trips correctly.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



📖 Description
Custom webhook bodies are rendered with
text/templateand sent withContent-Type: application/jsonby default, but the template had noJSON-escaping helper. Any value interpolated into a JSON string literal
(e.g.
{"text": "{{.Message}}"}) was inserted raw..Messageembedsev.Resourceverbatim, and for adiskalert that resource is a mountpointread from
/proc/mounts— a value that can (in unusual but real cases)contain a quote or backslash, producing a malformed request body.
This is low severity in practice (mountpoints aren't attacker-controlled
remote input; the realistic outcome is a broken webhook, not a
compromise), but it's a real robustness/correctness gap and the fix is
small.
This registers a
jsonfunction on the webhook template'sFuncMap:so template authors can write
{{json .Message}}(which supplies its ownquotes) instead of
"{{.Message}}". Adding a function to theFuncMapcannot break an existing template — templates that don't call
jsonrender exactly as before.
Updated the shipped example config
(
packaging/pimonitor.example.yaml) anddocs/ARCHITECTURE.md's "Alertdelivery" section to document and recommend the safe form.
docs/API.mddoesn't repeat the webhook template example, so no changewas needed there.
Deliberately out of scope (per the issue): switching to
html/templateor auto-escaping/auto-detecting JSON bodies, and
parseMountsnotdecoding
/proc/mountsoctal escapes (a separate, distinct bug).🎫 Issues
Closes #109
👩💻 Reviewer Notes
The core change is in
internal/alert/notify.go: the newtemplateFuncsvar and
.Funcs(templateFuncs)added to the template parse call inNewNotifier. Everything else is tests/docs.📑 Test Plan
Added to
internal/alert/notify_test.go, extending the existingtemplate-rendering tests in their established style:
TestNotifier_JSONHelperEscapesHostileResource— an event withResource: "/mnt/we\"ird\\path"rendered through{"text": {{json .Message}}}unmarshals cleanly and round-trips theexpected message.
TestNotifier_UnescapedTemplateProducesInvalidJSON— the same hostileevent through the old unescaped form
{"text": "{{.Message}}"}produces invalid JSON, documenting why the helper exists.
TestNotifier_JSONHelperHandlesNonStringValues—{{json .Value}}(float64) renders as a bare number and
{{json .At}}(time.Time) as aquoted RFC 3339 string.
jsonis alreadycovered by the existing
TestNotifier_RendersTemplateandTestNotifier_CustomContentType.Ran
go build ./...,go vet ./...,go test ./... -race -cover(all packages pass), and
golangci-lint run(0 issues).✅ Checklist
General
go test ./... -race -coverpasses locally).go vet ./...andgolangci-lint runare clean.ARCHITECTURE.mdif this changes a documented design decision.REST API / configuration / packaging
docs/API.mdto reflect a REST API change. (not applicable — no API shape change; API.md doesn't document the template example)/api/v1/...response shapes, or a new API version (/api/v2/...) was introduced instead.README.md/packaging/pimonitor.example.yamlto reflect a new or changed configuration option.packaging/install.shor the systemd units if this changes installation/packaging, and kept the unprivileged/privileged service split intact (seeSECURITY.md). (not applicable)⏭ Next Steps
parseMountsnot decoding/proc/mountsoctal escapes (\040,\011,\042,\134) is a genuine but distinct bug, called out in the issue as deserving its own issue rather than being folded into this PR.