Summary
knowns OS Command Injection via Insecure LSP Binary Path Config in .knowns/config.json
Advisory details
Overview
A critical Arbitrary Code Execution (ACE) vulnerability exists in the Knowns Language Server Protocol (LSP) detection and startup pipeline. The system blindly trusts the settings.lsp.languages.<lang>.binary field defined in the project-level .knowns/config.json file.
Because this field is never validated against an allowlist of managed binaries, and absolute paths are implicitly accepted, opening a malicious repository (or a legitimate repository where the config has been tampered with) results in the immediate execution of an attacker-controlled binary. The payload is executed twice per session: once during the initial runVersionCheck (health check), and again when the LSP server process is spawned via Server.Start(). When chained with the previously identified Config Overwrite vulnerabilities, this flaw yields a fully unauthenticated Remote Code Execution chain.
Affected paths
| File Path | Role | Vulnerability & Execution Impact |
|---|---|---|
internal/models/config.go |
Validation Gap | Missing Binary Validation (CWE-829): ProjectSettings.Validate() enforces duration formats for task lifecycles but completely ignores the LSPLanguageSettings.Binary field. Absolute paths, shell interpreters, and untrusted executables are silently accepted. |
internal/lsp/detect.go |
Execution Sink #1 | Unsanitized Health Check Execution: Detector.resolve() passes the unvalidated override to exec.LookPath(), then invokes runVersionCheck() which blindly calls cmd.Run() on the attacker-controlled binary with CheckArgs. |
internal/lsp/server.go |
Execution Sink #2 | Unsanitized LSP Server Spawn: Server.Start() passes the resolved binary path to knownsprocess.Command() and spawns it as a long-running background process via cmd.Start(), executing the payload a second time. |
Root Cause
Missing Validation in Configuration Schema
In internal/models/config.go, the ProjectSettings.Validate() function is responsible for sanitizing the project configuration loaded from .knowns/config.json. However, it only validates task lifecycle durations:
func (s ProjectSettings) Validate() error {
settings := s.EffectiveTaskLifecycle()
if _, err := ParseTaskLifecycleDuration(settings.ArchiveAfter); err != nil { ... }
// NO VALIDATION FOR s.LSP.Languages[lang].Binary
return nil
}
The LSPLanguageSettings struct exposes a Binary string field. When a malicious project is loaded, this string is passed unmodified into the LSP resolution pipeline.
Blind Execution in LSP Detector
In internal/lsp/detect.go, the Detector.resolve() function accepts an override string (from the config) and forces it into the execution pipeline:
func (d *Detector) resolve(ctx context.Context, root string, lang Language, override string) (ServerCommand, bool) {
binaries := lang.Binaries
if override != "" {
binary := Binary{Name: override} // Attacker's malicious path injected here
binaries = []Binary{binary}
}
for _, binary := range binaries {
path, err := d.LookPath(binary.Name) // Accepts absolute paths (e.g., /tmp/evil.sh)
// ...
err = d.RunCheck(checkCtx, path, binary.CheckArgs...) // EXECUTION #1
// ...
}
}
d.RunCheck maps to runVersionCheck, which executes the binary via knownsprocess.CommandContext(ctx, path, args...).Run().
Unrestricted Process Spawn
In internal/lsp/server.go, when the LSP manager starts the server, it executes the same compromised path:
func (s *Server) Start(ctx context.Context) error {
// ...
cmd := knownsprocess.Command(s.Command.Path, s.Command.Args...) // EXECUTION #2
cmd.Dir = s.Root
// ...
if err := cmd.Start(); err != nil { ... }
}
Attack Vector
| Phase | Request / Action | Effect |
|---|---|---|
| 1. Plant | Attacker commits .knowns/config.json containing {"settings":{"lsp":{"languages":{"go":{"binary":"/tmp/evil.sh"}}}}} to a repository. |
Malicious config embedded in the project. |
| 2. Trigger | Victim (or AI Agent) clones the repo and opens it with knowns mcp or knowns browser. |
Auto-detection scans the project, finds a .go file, and loads the malicious config. |
| 3. Execute (Health Check) | Detector.resolve() calls runVersionCheck("/tmp/evil.sh", "version"). |
RCE Execution #1 occurs silently in the background. |
| 4. Execute (LSP Spawn) | Server.Start() calls cmd.Start() with the same binary. |
RCE Execution #2 occurs, spawning the malicious process as a long-running daemon. |
Chained Attack Vector (Unauthenticated RCE):
If combined with the Config Overwrite vulnerability (via code.replace path traversal), a remote attacker can overwrite .knowns/config.json in a target project, inject a payload, and trigger an LSP restart (via docs.update or server reload), achieving Remote Code Execution without any user interaction.
Analysis
This vulnerability is a textbook example of Inclusion of Functionality from Untrusted Control Sphere (CWE-829), commonly known in the IDE/Editor space as the "Malicious Workspace" vulnerability (similar to historical CVEs in VS Code where .vscode/settings.json could point to malicious interpreter paths).
The core failure is the lack of a strict allowlist for executable paths. By relying on exec.LookPath(), the code allows an attacker to bypass binary name resolution simply by providing an absolute path (/abs/path/evil.sh) or a path containing a slash (./evil.sh).
Furthermore, the execution happens automatically upon project load. In modern AI-driven development workflows, AI Agents automatically scan project files (like .go, .ts, .py) to provide context. The LSP detector triggers on file extensions, meaning the victim or agent does not even need to explicitly run a "build" or "start" command; the mere act of the Agent indexing the workspace triggers the payload.
Fix
Patch is available right now at New Release.
References
- https://github.com/advisories/GHSA-mc52-mwq4-vfx3
- https://github.com/knowns-dev/knowns/security/advisories/GHSA-mc52-mwq4-vfx3
- https://nvd.nist.gov/vuln/detail/CVE-2026-86540
- https://github.com/knowns-dev/knowns/commit/d3989829fb5095666d23d005b2f78a082832a396
- https://github.com/knowns-dev/knowns/blob/v0.29.1/internal/lsp/detect.go#L128-L157
- https://github.com/knowns-dev/knowns/blob/v0.29.1/internal/models/config.go#L205-L216
- https://github.com/knowns-dev/knowns/releases/tag/v0.30.0
- https://www.vulncheck.com/advisories/knowns-before-0.30.0-arbitrary-code-execution-via-lsp-binary
Related vulnerabilities
All Supply chain →- HIGHCVE-2026-61446
PraisonAI: Plugin Auto-Discovery Executes Arbitrary Python Files Without Verification
- HIGHCVE-2026-105677
Ghost: Remote Code Execution via Theme Translation Files
- CRITICALGHSA-jqmf-mx4f-hfr6
Vibe-Trading LLM-callable tools permit command execution, code injection, and SSRF
- HIGHCVE-2026-87817
GitPython: Repository content can impersonate the git directory, leading to arbitrary code execution
- CRITICALSC-PPE-CICDSEC4-2022
Poisoned Pipeline Execution is the class of attack in which an actor with write access to source control, but no direct access to the build environment, injects attacker-controlled commands that the CI pipeline then executes with its own privileges, secrets, and tokens. Direct PPE (D-PPE) modifies the CI configuration file itself (for example .github/workflows, .gitlab-ci.yml, or a Jenkinsfile) by pushing to an unprotected branch or opening a pull request, so the new pipeline steps run on trigger. Indirect PPE (I-PPE) instead poisons files the pipeline already references, such as a Makefile, test harness, build script, or linter config, when the config is protected but the referenced code is not. Public PPE (3PE) abuses public and open-source repositories that run unreviewed code from anonymous fork pull requests, frequently via the dangerous pull_request_target trigger that grants the fork workflow access to repository secrets. The pattern is catalogued as CICD-SEC-4 in the OWASP Top 10 CI/CD Security Risks (published September 2022) and in Cider/Legit Security research, with real cases including public-repo PPE in popular projects and GitHub Actions workflows abused for cryptocurrency mining.
- HIGHSC-GHA-SCRIPT-INJECTION-2020
GitHub Security Lab documented (initial guidance August 20, 2020, updated since) a widespread GitHub Actions vulnerability class in which attacker-controlled event fields interpolated into run shell steps cause command execution. Because expressions in double-brace syntax are evaluated and substituted into the temporary shell script before the runner executes it, embedding an untrusted field such as github.event.issue.title or github.event.pull_request.head_ref directly in a run block lets the attacker break out of the intended command. An attacker who simply opens an issue or PR with a title containing a quote and a shell payload (for example a backtick command-substitution or a semicolon-curl sequence) executes arbitrary commands on the runner, reading environment variables and exfiltrating GITHUB_TOKEN and repository secrets to an external host. This is expression/script injection via untrusted github.event input, found across a large number of public-repo workflows. The fix is to never interpolate untrusted context into a shell; pass it through an intermediate quoted environment variable so it is treated as data, not script.