yoloshii/ClawMem
criticalOn-device memory layer for AI agents. Claude Code, Hermes and OpenClaw. Hooks + MCP server + hybrid RAG search.
MCP server (purpose undetermined)
525function handleExport(_req: Request, _url: URL, store: Store): Response {
526 const docs = store.db.prepare(`
527 SELECT d.id, d.collection, d.path, d.title, d.content_type, d.confidence,
528 d.access_count, d.quality_score, d.pinned, d.created_at, d.modified_at,
529 d.duplicate_count, d.revision_count, d.topic_key, d.normalized_hash,
530 c.doc as body
531 FROM documents d
532 JOIN content c ON c.hash = d.hash
533 WHERE d.active = 1
534 ORDER BY d.collection, d.path
535 `).all() as any[];
536
537 return jsonResponse({
538 version: "1.0.0",
539 exported_at: new Date().toISOString(),
540 count: docs.length,
541 documents: docs,
542 });
543}// Exploitable if MCP is exposed to network (network_exposed) or if a compromised LLM can make HTTP requests to localhost.
The /export endpoint returns all document bodies (including full content) without any authentication check. While the server has optional Bearer token auth, if CLAWMEM_API_TOKEN is not set, this endpoint is completely open. Even with auth, the endpoint dumps all documents in bulk, which is excessive for the intended purpose of a memory server.
ImpactAn attacker can exfiltrate all stored documents, including potentially sensitive information, in a single request.
FixAdd authentication requirement to the export endpoint, or remove it if not needed. If kept, require admin-level auth and add rate limiting.
214function handleGetDocument(_req: Request, url: URL, store: Store): Response {
215 const docid = url.pathname.split("/").pop();
216 if (!docid) return jsonError("docid is required");
217
218 const result = store.findDocument(docid, { includeBody: true });
219 if ("error" in result) {
220 return jsonError(`Document not found: ${docid}`, 404);
221 }
222
223 return jsonResponse({
224 docid: result.docid,
225 path: result.displayPath,
226 title: result.title,
227 collection: result.collectionName,
228 modifiedAt: result.modifiedAt,
229 bodyLength: result.bodyLength,
230 body: result.body,
231 context: result.context,
232 });
233}// Exploitable if MCP is exposed to network (network_exposed) or if a compromised LLM can make HTTP requests to localhost.
The /documents/:docid endpoint returns the full body of any document by its docid. Similarly, /documents?pattern=... returns bodies for all matching documents. These endpoints have no access control beyond optional Bearer token. If the token is not set, any network attacker can read all documents.
ImpactAn attacker can read the full content of any stored document, potentially exposing sensitive information.
FixAdd authentication to all document retrieval endpoints. Consider limiting body exposure or requiring explicit consent.
38const API_TOKEN = process.env.CLAWMEM_API_TOKEN || null;
39
40function checkAuth(req: Request): Response | null {
41 if (!API_TOKEN) return null; // No token configured — open access
42 const auth = req.headers.get("authorization");
43 if (!auth || auth !== `Bearer ${API_TOKEN}`) {
44 return jsonResponse({ error: "Unauthorized" }, 401);
45 }
46 return null;
47}// Exploitable if MCP is exposed to network (network_exposed) or if a compromised LLM can make HTTP requests to localhost.
The server's authentication is optional and based solely on a static Bearer token. When no token is configured, all endpoints are open. Even with a token, the token is static and shared, providing no granular access control. The server exposes powerful endpoints like /export (full database dump), /reindex (reindex all collections), and /lifecycle/sweep (archive/purge documents) that should require elevated privileges.
ImpactAn attacker with network access can perform administrative actions like exporting all data, reindexing, or archiving documents without any authentication.
FixMake authentication mandatory. Implement role-based access control for sensitive endpoints. Use per-user tokens or session-based auth.
421async function handleLifecycleRestore(req: Request, _url: URL, store: Store): Promise<Response> {
422 const body = await parseBody<{ query?: string; collection?: string }>(req);
423
424 const filter: { ids?: number[]; collection?: string; sinceDate?: string } = {};
425 if (body?.collection) filter.collection = body.collection;
426
427 const restored = store.restoreArchivedDocuments(filter);
428 return jsonResponse({ restored });
429}// Exploitable if MCP is exposed to network (network_exposed) or if a compromised LLM can make HTTP requests to localhost.
The /lifecycle/restore endpoint accepts a collection name from the request body without validation. The collection name is passed directly to store.restoreArchivedDocuments, which may use it in SQL queries or file operations. Similarly, /lifecycle/sweep uses the lifecycle policy from config but does not validate the collection name in the request.
ImpactPotential for SQL injection or unintended operations if the collection name is used unsafely in database queries.
FixValidate the collection name against the list of configured collections before using it in any operations.
493async function handleReindex(req: Request, _url: URL, store: Store): Promise<Response> {
494 const body = await parseBody<{ collection?: string }>(req);
495
496 const { indexCollection } = await import("./indexer.ts");
497 const collections = listCollections();
498 const targetCollections = body?.collection
499 ? collections.filter(c => c.name === body.collection)
500 : collections;
501
502 if (targetCollections.length === 0) {
503 return jsonError(`Collection not found: ${body?.collection}`, 404);
504 }
505
506 let totalAdded = 0, totalUpdated = 0, totalRemoved = 0;
507
508 for (const coll of targetCollections) {
509 const stats = await indexCollection(store, coll.name, coll.path, coll.pattern);
510 totalAdded += stats.added;
511 totalUpdated += stats.updated;
512 totalRemoved += stats.removed;
513 }
514
515 return jsonResponse({
516 collections: targetCollections.length,
517 added: totalAdded,
518 updated: totalUpdated,
519 removed: totalRemoved,
520 });
521}// Exploitable if MCP is exposed to network (network_exposed) or if a compromised LLM can make HTTP requests to localhost.
The /reindex endpoint accepts a collection name from the request body and uses it to filter collections. While it does check against the configured collections list, the collection name is not validated for path traversal or special characters. The indexCollection function (imported from indexer.ts) likely uses the collection's path to read files from disk. If a malicious collection name could somehow bypass the filter, it could lead to arbitrary file reads.
ImpactPotential for path traversal or arbitrary file indexing if the collection name filter can be bypassed.
FixValidate that the collection name matches expected patterns (alphanumeric, hyphens, underscores) and ensure the filter is strict.
38const API_TOKEN = process.env.CLAWMEM_API_TOKEN || null;// Local-only MCP, requires compromised LLM to exploit. Low severity because environment variable exposure typically requires local access.
The server reads the API token from an environment variable. While this is a common pattern, the token is stored in plaintext in the process environment and could be exposed through error messages, debug logs, or process dumps. Additionally, the embedding API key (CLAWMEM_EMBED_API_KEY) is also read from environment and sent as a Bearer token in HTTP requests.
ImpactAn attacker who gains access to the process environment or logs could obtain the API token and use it to authenticate to the server.
FixUse a secrets manager or encrypted storage for credentials. Ensure error messages do not leak sensitive values. Consider using short-lived tokens.