T09 · Insecure Skill Coding Practices
- Location
scripts/memory_cli.js:348- Finding
Private Memory Content Leaks Through Unfiltered Summary and Index Files
- Content
View full analysis
Vulnerability Details
File Location:
scripts/memory_cli.js, lines 348–353 and 368–418
Vulnerability Type: Private-data exposure through derived artifacts
Risk Level: MediumVulnerable Code
js function saveMemories(storePath, memories) { ensureStoreDir(storePath); const memFile = path.join(storePath, "memories.json"); atomicWriteJson(memFile, memories); rebuildIndex(storePath, memories); updateSummary(storePath, memories); }js /** Maintain SUMMARY.md — a human/agent-readable snapshot of top memories. */ function updateSummary(storePath, memories) { if (!memories || memories.length === 0) { fs.writeFileSync(path.join(storePath, "SUMMARY.md"), "# Memory Store Summary\n\n_No memories yet._\n", "utf8"); return; } // Rank: composite score descending, take top 10 const ranked = memories .filter((m) => m.status !== "archived") .map((m) => ({ mem: m, score: summaryScore(m) })) .sort((a, b) => b.score - a.score) .slice(0, 10); const lines = []; lines.push("# Memory Store Summary\n"); lines.push(`_Last updated: ${nowISO()}_\n`); lines.push(`Total memories: ${memories.length} | Showing top ${ranked.length}\n`); lines.push("---\n"); for (const { mem, score } of ranked) { const icon = TYPE_ICON[mem.type] || "📄"; const p = mem.priority || "P2"; const imp = mem.importance || "—"; const scope = mem.scope === "workspace" ? "📁" : "🌐"; lines.push(`### ${icon} ${mem.type}: ${mem.title}`); lines.push(`**Priority:** ${p} · **Importance:** ${imp} · **Scope:** ${scope} ${mem.scope || "global"}`); if (mem.summary) lines.push(mem.summary); if (mem.tags && mem.tags.length) lines.push(`\`tags: ${mem.tags.join(", ")}\``); lines.push(""); } lines.push("---\n"); lines.push("_Run `memory-store search --query \"...\"` for full-text search._\n"); fs.writeFileSync(path.join(storePath, "SUMMARY.md"), lines.join("\n"), "utf8"); } function rebuildIndex(s ...[truncated 2840 chars]- Remediation
View remediation
Remediation Suggestions
- Exclude private records from shared derived artifacts:
js const publicMemories = memories.filter( (memory) => memory.visibility !== "private" ); rebuildIndex(storePath, publicMemories); updateSummary(storePath, publicMemories);-
If private summaries and indexes are required, generate separate per-agent artifacts. Use sanitized Agent identifiers, assign explicit ownership metadata, and ensure that only the matching identity can select them through the CLI.
-
Do not place private titles, summaries, details, tags, identifiers, or metadata in any shared index. Indexing only identifiers is insufficient if associated tokens reveal the underlying content.
-
Create global memory directories with mode
0700and memory, archive, summary, index, configuration, and temporary files with mode0600. Verify existing objects and safely tighten overly broad permissions where supported. -
Preserve restrictive permissions during atomic replacement by creating the temporary file with mode
0600and validating the final file mode afterrenameSync(). -
Add regression tests that store distinctive marker strings in private titles, summaries, details, and tags, then assert that none appear in shared
SUMMARY.mdormemories.index.json. -
Add tests covering mixed public and private records, global and workspace stores, updates, merges, recalls that modify access counters, archive operations, and migration paths because each may trigger derived-artifact regeneration.
