Skip to main content

Command Palette

Search for a command to run...

Abusing an Unauthenticated Local Server to Overwrite LLM Wiki

Updated
4 min read
Abusing an Unauthenticated Local Server to Overwrite LLM Wiki

Local HTTP servers in desktop apps are easy to overlook during a security review. They don't show up in bug bounty scopes, they're not publicly routable, and developers rarely treat them as a trust boundary. LLM Wiki — a Tauri-based knowledge base app with close to 10k GitHub stars — has one running on port 19827.

What LLM Wiki Does

LLM Wiki is a cross-platform desktop app built on Tauri (Rust backend, React frontend). Instead of traditional RAG, it reads your documents and uses an LLM to build a persistent, self-updating wiki — pages get created, linked, and revised automatically as you add sources. There's also a browser extension that lets you clip web content directly into your wiki.

That extension communicates with the app through the local HTTP server on port 19827. That's the attack surface.

Opening the Code

Cloned the repo, opened src-tauri/src/clip_server.rs. Pure Rust HTTP server, no framework, handles requests manually.

First thing in the CORS section:

Header::from_bytes("Access-Control-Allow-Origin", "*").unwrap(), Wildcard. Every origin. No exceptions.

This alone confirms CSRF is possible — any website can send cross-origin requests to this server. The question is what those requests can do.

Read through the POST /clip handler — the endpoint that receives clipped content from the browser extension and saves it to the wiki. The projectPath handling:

} else {

project_path_from_body // taken directly from request body

};

let dir_path = std::path::Path::new(&project_path) .join("raw").join("sources");

std::fs::create_dir_all(&dir_path)?;

// writes file here

No validation. No allowlist. Pass in any path and it writes there.

That's arbitrary file write. And it's reachable cross-origin because of the wildcard CORS.

Putting the Chain Together

Before writing a PoC, I checked the other endpoints. GET /projects returns the full filesystem paths of every wiki project the user has. No authentication. No token. Just call it.

The complete attack chain:

  1. Victim visits a page the attacker controls

  2. Page calls GET http://127.0.0.1:19827/projects → returns full filesystem paths of all wiki projects

  3. For each path, page calls POST /clip with projectPath set to that path and attacker-controlled content

  4. Every wiki project gets overwritten — victim clicked nothing, saw nothing, lost everything

Three bugs. One page visit. The wildcard CORS makes the cross-origin requests possible, /projects provides the targets, /clip does the damage.

Where AI Fit Into This

Most of this audit was done alongside Claude Code running in the terminal. Not to find bugs automatically — to read code faster.

234 TypeScript files solo would have taken days. With AI, the workflow became: point it at a file or a pattern, ask whether there's anything that processes user input, follow the data flow. The speed difference is real.

Where it saved the most time was filtering false positives. The TOCTOU in clip_server.rs looks alarming on first read — a while file_path.exists() check followed by a write, classic TOCTOU pattern. Walking through the context: race window is microseconds, no privilege escalation path, no reliable exploit. Low severity. Without AI helping reason through it, that analysis would have taken longer to feel confident about.

The Fix

Two fixes were submitted in PR #272.

Replacing wildcard CORS with an allowlist:

const ALLOWED_ORIGINS: &[&str] = &[

"http://localhost:1420",

"tauri://localhost"

];

Requests from any other origin now receive Access-Control-Allow-Origin: null, which browsers treat as a blocked response.

Validating projectPath against registered projects:

let is_registered = current == normalized

|| projects.iter().any(|(_, p)| p == normalized);

if !is_registered {

return r#"{"ok":false,"error":"projectPath is not a registered project"}"# .to_string(); }

Any path not in the user's registered project list is rejected before the file write happens.

The Takeaway

Any website a user visits can send requests to 127.0.0.1. Developers building desktop apps with local HTTP servers tend to treat localhost as a trusted zone — it isn't. As far as the browser is concerned, localhost is just another origin, and wildcard CORS removes the only enforcement mechanism that would otherwise protect it.

If you're building something similar: treat your local HTTP server like a public API. Enforce an origin allowlist on CORS. Validate every input parameter against what the application actually knows about.