{"id":"CVE-2026-44884","aliases":["GHSA-cqpq-2fgr-8mvc","GO-2026-5333"],"url":"https://o3.security/vulnerability/CVE-2026-44884","summary":"Portainer: Missing authorization on custom template file endpoint exposes template content","details":"## Summary\nA missing authorization vulnerability in the Custom Template file endpoint (`GET /api/custom_templates/{id}/file`) allows any authenticated user to read the file content of any custom template by enumerating sequential integer IDs, bypassing Resource Control access restrictions. Template files may contain environment-specific values such as connection strings, API tokens, or registry credentials that administrators would not expect standard users to read.\n\n## Severity\n\n**Medium**\n\n**CWE-862** — Missing Authorization\n\nExploitation requires an authenticated user account and at least one custom template to exist. Template files are returned verbatim and may contain embedded credentials.\n\n## Affected Versions\n\nThe vulnerability exists in every Portainer release since custom templates were introduced — the `customTemplateFile` handler has never performed an authorization check.\n\nFixes are included in the following releases:\n\n| Branch              | First vulnerable | Fixed in   |\n|---------------------|------------------|------------|\n| 2.33.x (LTS)        | 2.33.0           | **2.33.8** |\n| 2.39.x (LTS)        | 2.39.0           | **2.39.1** |\n\nPortainer 2.40.0 and later are not affected — the fix was already on `develop` when the 2.40.x STS line branched. Portainer LTS branches receive fixes for 6 months plus a 3-month overlap after the next LTS ships. All releases **prior to 2.33.0 are end-of-life** and will not receive a fix; users on EOL versions should upgrade to a supported release.\n\n## Workarounds\n\nThere is no runtime configuration that blocks the vulnerable endpoint directly. Administrators who cannot immediately upgrade can reduce exposure by:\n\n- **Avoiding storing secrets in custom templates** until the patched release is deployed. Move sensitive configuration values to Portainer environment variables or an external secret store.\n- **Reviewing existing custom templates** for embedded secrets. Assume any secret previously stored in a custom template on an unpatched instance has been exposed to every authenticated user and rotate accordingly.\n\nNeither replaces the fix.\n\n## Affected Code\nThe `customTemplateFile` handler in `api/http/handler/customtemplates/customtemplate_file.go` (lines 30-53) retrieves a custom template by its numeric ID and returns the file content without performing any authorization check.\n\nAll other custom template endpoints properly verify access:\n\n| Endpoint | Method | Authorization Check |\n|----------|--------|-------------------|\n| `/api/custom_templates/{id}` | GET (inspect) | `userCanEditTemplate()` + `UserCanAccessResource()` |\n| `/api/custom_templates/{id}` | PUT (update) | `userCanEditTemplate()` |\n| `/api/custom_templates/{id}` | DELETE | `userCanEditTemplate()` |\n| `/api/custom_templates` | GET (list) | `FilterAuthorizedCustomTemplates()` |\n| **`/api/custom_templates/{id}/file`** | **GET** | **None** |\n\n**Vulnerable code** (`customtemplate_file.go:30-53`):\n```go\nfunc (handler *Handler) customTemplateFile(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {\n    customTemplateID, _ := request.RetrieveNumericRouteVariableValue(r, \"id\")\n    customTemplate, _ := handler.DataStore.CustomTemplate().Read(portainer.CustomTemplateID(customTemplateID))\n    // NO AUTHORIZATION CHECK\n    fileContent, _ := handler.FileService.GetFileContent(customTemplate.ProjectPath, entryPath)\n    return response.JSON(w, &fileResponse{FileContent: string(fileContent)})\n}\n```\n\n**Secure reference** (`customtemplate_inspect.go:50-75`):\n```go\ncanEdit := userCanEditTemplate(customTemplate, securityContext)\nhasAccess := authorization.UserCanAccessResource(securityContext.UserID, teamIDs, resourceControl)\nif !canEdit && !hasAccess {\n    return httperror.Forbidden(\"Access denied to resource\", httperrors.ErrResourceAccessDenied)\n}\n```\n\n## Impact\nAny authenticated user (including the lowest-privilege standard user) can read the file content of every custom template in the instance. Custom templates commonly contain Docker Compose configuration, which may include environment-specific secrets such as database connection strings, API tokens, or registry credentials.\n\n## Timeline\n\n- 2026-02-11: Reported via GitHub Security Advisory by **duddnr0615k**.\n- 2026-03-04: Fix merged to `develop` and cherry-picked to `release/2.39`.\n- 2026-03-19: 2.39.1 released with fix.\n- 2026-03-25: 2.40.0 released with fix already present from branch cut.\n- 2026-05-07: 2.33.8 released.\n\n## Credit\n\n- **duddnr0615k** — identified and reported the missing authorization check on the custom template file endpoint.","published":"2026-05-28T20:58:36.516Z","modified":"2026-08-12T03:51:33.330950154Z","cvss":null,"epss":{"score":0.00257,"percentile":0.17621,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/portainer/portainer","fixedVersion":"2.33.8"},{"ecosystem":"Go","name":"github.com/portainer/portainer","fixedVersion":"2.39.1"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/44xxx/CVE-2026-44884.json"},{"type":"ADVISORY","url":"https://github.com/portainer/portainer/security/advisories/GHSA-cqpq-2fgr-8mvc"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-44884"},{"type":"PACKAGE","url":"https://github.com/portainer/portainer"},{"type":"WEB","url":"https://github.com/portainer/portainer/releases/tag/2.33.8"},{"type":"WEB","url":"https://github.com/portainer/portainer/releases/tag/2.39.2"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:33.330950154Z"}}