# 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.](https://github.com/nashsu/llm_wiki/pull/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.
