Scope and safety. This article analyses a fixed, publicly disclosed vulnerability using the vendor’s own published source references. It describes the defective data flow and the remediation. It does not include working traversal strings, target file lists tuned for exploitation, or a runnable proof of concept.
Executive summary
CVE-2026-86538 is a path-traversal vulnerability in knowns before 0.30.0. The POST /api/templates/preview endpoint accepted a caller-supplied templateFile value, joined it onto the resolved template directory with filepath.Join, read the resulting path from disk, and returned the contents in the JSON response.
filepath.Join cleans the path it produces. It does not constrain the result to the base directory. A templateFile containing parent-directory segments therefore resolves outside the template root, and the endpoint happily reads and returns it.
The weakness is CWE-22: Improper Limitation of a Pathname to a Restricted Directory. The vendor scores it CVSS 4.0 8.7 High (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N) and CVSS 3.1 7.5 High (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) — confidentiality-only, but complete.
The PR:N in that vector is not an accident. It is the direct consequence of CVE-2026-86543, which left the management API unauthenticated on all interfaces by default. The two together make this a credential-free remote file-read primitive against a developer’s machine.
Fixed in knowns 0.30.0 by commit 09c5a96fd581.
Evidence labels used below:
- Verified means stated by the CVE record, the GitHub Security Advisory, or the project’s published source and release material.
- Inferred means a deployment consequence derived from those facts and clearly marked as such.
Key facts
| Item | Finding | Confidence |
|---|---|---|
| Identifier | CVE-2026-86538 | Verified |
| Advisory | GHSA-fpxv-c555-rhm3 | Verified |
| Product | knowns (pkg:npm/knowns, Go implementation) |
Verified |
| Affected versions | All versions before 0.30.0 | Verified |
| Fixed version | 0.30.0 | Verified |
| Weakness | Path traversal, CWE-22 | Verified |
| Entry point | templateFile field of POST /api/templates/preview |
Verified |
| Dangerous sink | os.ReadFile on a filepath.Join result |
Verified |
| Authentication required | None, by default | Verified |
| Impact | Disclosure of any file readable by the server process | Verified |
| CVSS 4.0 | 8.7 High | Verified |
| CVSS 3.1 | 7.5 High | Verified |
| Reporters | Tong Hoang Gia, Nguyen Huy Hoang | Verified |
| Fix commit | 09c5a96fd5817b941dc86669278c1a17db10ed4e |
Verified |
Why a “preview” endpoint is a file-read primitive
Template preview features share a recognisable shape across products: take a template identifier, take a file name inside that template, render it with some variables, show the result. Each step looks harmless. The composite is a request-controlled read whose output is returned verbatim to the caller.
Three properties make this variant more serious than the average traversal bug:
- The output is the file, not a side effect. Some traversal bugs only leak existence or a boolean. This one returns content in the response body.
- There is no extension constraint. The default is
main.hbs, but nothing requires the supplied value to end in.hbs. Any readable path is fair game. - The process runs as the developer. knowns is a developer tool, so the reading process holds the developer’s own filesystem privileges — not a hardened service account.
Inferred: the interesting targets are therefore not project files, which the caller could often read through legitimate endpoints anyway, but the developer’s ambient secrets: environment files, cloud credential directories, SSH material, shell history, and the tool’s own configuration.
Root-cause analysis
The vulnerable handler in internal/server/routes/templates.go at v0.29.1:
1 | // POST /api/templates/preview |
The bug is one line, and it is a line that appears in a great deal of Go code:
1 | hbsPath := filepath.Join(tmpl.Path, templateFile) |
The filepath.Join misconception
filepath.Join joins elements and calls Clean on the result. Developers frequently read “cleans the path” as “makes the path safe”. It does not. Clean resolves .. segments lexically, which means it happily produces a path above the base directory when the untrusted element asks for it. Joining a base with ../../x yields a sibling of the base’s parent, not an error and not a path inside the base.
Nothing in the handler re-checks the result. There is no strings.HasPrefix containment check, no filepath.Rel verification, and no symlink resolution.
The variable substitution runs after the read
1 | rendered := string(hbsContent) |
Substitution only replaces {{name}} placeholders that happen to appear in the content. A file with no placeholders passes through unchanged, so the response contains the raw file. Inferred: this makes the primitive clean rather than lossy — the attacker does not have to reconstruct content from partial rendering artefacts.
The data flow
1 | POST /api/templates/preview (unauthenticated by default) |
Note that Templates.Get(req.Name) is validated — the template must exist. The developer applied validation to the parameter that felt like an identifier and omitted it on the parameter that felt like a filename. Inferred: that asymmetry is a common and instructive pattern; the field that looks most like a path is the one that most needs a containment check, and it is often the one that gets the least.
Impact
Verified impact
- An unauthenticated network caller could read arbitrary files accessible to the server process.
- Contents are returned directly in the JSON response.
- The CVE record specifically cites credentials and configuration as exposed material.
- Integrity and availability are not affected by this issue in isolation (
VI:N/VA:N).
Deployment-dependent consequences
The following are inferred:
- disclosure of
.envfiles, cloud credential files, API tokens, and CI secrets present on the host; - disclosure of SSH private keys and configuration, and of the knowns configuration itself;
- disclosure of source code, internal specifications, and architectural decisions outside the current project;
- reconnaissance that makes the write-side issues in this set more effective, because an attacker who can read
.knowns/config.jsonand the project layout knows exactly where to aim CVE-2026-86542; - onward compromise of any service whose credentials were recovered — the blast radius is set by what the developer’s account can reach, not by knowns.
Inferred: for a developer workstation, a reliable arbitrary file read is frequently equivalent to a credential compromise, because development machines are where long-lived tokens accumulate.
Safe validation and detection
Inventory and configuration review
- Identify knowns installations and record versions; anything before 0.30.0 is affected.
- Determine whether the management API was reachable beyond loopback — see CVE-2026-86543 for the bind-address and password checks.
- Confirm the exposure window: how long did an affected version run, on what networks, and was a tunnel ever started?
- Inventory what was readable by that user account during the window. That set, not the project directory, is the exposure scope.
Do not validate by sending traversal payloads at a production or shared host. Version and bind-address facts answer the question without touching the vulnerable path.
Telemetry to review
POST /api/templates/previewrequests wheretemplateFileis present and is not a plain relative name;- any
templateFilevalue containing parent-directory segments, absolute path prefixes, or backslashes; - preview requests from source addresses other than loopback;
- response sizes on the preview endpoint that are far outside the normal distribution for templates;
- bursts of preview requests iterating over similar names, which is the signature of enumeration rather than use;
- reads of sensitive files by the knowns process in host filesystem audit logs (auditd, EDR, or equivalent).
Inferred: the last item is the most reliable signal, because it observes the sink rather than the request. Request logging can be absent or truncated, but a process-level read of a credential file by a documentation tool is anomalous on its face.
Remediation and hardening
Required remediation
Upgrade to knowns 0.30.0 or later. The fix introduces a dedicated internal/safepath package and routes the preview through it:
1 | hbsPath, err := safepath.Resolve(tmpl.Path, templateFile) |
safepath.Resolve is the interesting part of the patch, because it treats path validation as a real parsing problem rather than a substring check:
1 | // Package safepath resolves untrusted filesystem paths inside an explicit root. |
Worth noting about that implementation:
- it rejects rather than sanitises, so there is no strip-and-retry logic to outsmart;
- it normalises backslashes before segmenting, so Windows separators cannot smuggle a segment past a Unix-oriented check;
- it rejects NUL bytes, Windows device prefixes (
\\?\,\\.\), volume and UNC prefixes, drive-relative paths, and NTFS alternate data streams; - it resolves against an absolute, cleaned root and — in the
ResolveProjectRealvariant — follows symlinks so a link inside the root cannot point outside it.
The same commit applies containment to the document store, memory store, and template store; those paths are covered in CVE-2026-86439.
Defense in depth
- Bind the server to loopback and set a password (both are enforced by default in 0.30.0).
- Run developer context servers with the narrowest practical filesystem view; a container or a dedicated user account bounds what any future read primitive can reach.
- Keep long-lived cloud and registry credentials out of the developer home directory; prefer short-lived, brokered credentials.
- Rotate any secret that was readable by the account during the exposure window if you cannot rule out access.
- Where you must accept a filename from a client, treat the allowed set as an enumeration (a whitelist of known template files) rather than a path at all.
Timeline
| Date | Event | Source certainty |
|---|---|---|
| 2026-08-15 | Fix commit 09c5a96fd581 authored in the knowns repository |
Repository metadata |
| 2026-08-16 | Public disclosure date recorded on the CVE record | CVE record datePublic |
| 2026-09-07 | CVE-2026-86538 reserved and published | CVE record metadata |
| 2026-09-08 | This analysis published | This site |
Lessons for builders
filepath.Join is not a security boundary
This is the single most reusable takeaway. Join composes and cleans; it never constrains. Any time an untrusted element is joined onto a trusted base, the result must be validated against that base afterwards — or, better, produced by a function whose contract is containment.
Reject, do not sanitise
Stripping .. from a string is a losing game: encodings, mixed separators, and repeated sequences all defeat naive filters. The 0.30.0 approach — normalise, then refuse anything that is not a plain relative path inside the root — has no residual cleverness to be outwitted.
Validation applied unevenly is validation missing
req.Name was validated. req.TemplateFile was not. The handler’s author clearly understood that input needed checking; the check simply did not follow the value that reached the filesystem. Trace each parameter to its sink independently.
Confidentiality-only findings still deserve High
A vector with VI:N/VA:N can read as “just an info leak”. On a developer workstation, a general file read is the credential-collection step of an intrusion. Score the primitive, not the immediate symptom.
Centralise the dangerous operation
The most durable part of this fix is not the changed line — it is that safepath now exists. One reviewed, tested implementation that every call site uses is far more defensible than a containment check re-derived at each handler, and it gives future contributors an obvious right answer.
Related advisories in this set
- CVE-2026-86543 — unauthenticated management API exposure (the reason this one is
PR:N) - CVE-2026-86542 — the write-side counterpart, via import name
- CVE-2026-86439 — path traversal through MCP doc and memory tools
- CVE-2026-86541 — path traversal in the
code.replaceMCP action - CVE-2026-86544 — authorisation bypass via misclassified code actions
- CVE-2026-86540 — arbitrary code execution via the LSP binary override
- CVE-2026-86775 — unauthenticated path traversal through the HTTP Document API
References
- CVE.org record: CVE-2026-86538
- NIST National Vulnerability Database: CVE-2026-86538
- GitHub Security Advisory: GHSA-fpxv-c555-rhm3
- VulnCheck advisory
- Vulnerable
previewhandler at v0.29.1 - Fix commit
09c5a96fd581 - knowns v0.30.0 release notes
Reported by Tong Hoang Gia and Nguyen Huy Hoang. Recheck the primary sources above during remediation; public vulnerability records are frequently revised after publication.