T05 · Unauthorized Access and Privilege Escalation
Warning
- Location
- scripts/send-postcard.mjs:98
- Finding
- Automatic Disclosure of Persona File Content to a Third-Party Service<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send-postcard.mjs:98-170` **Vulnerability Type**: Automatic sensitive-data disclosure and excessive file access **Risk Level**: Medium ### Vulnerable Code ```js const WORKSPACE = process.env.OPENCLAW_WORKSPACE || resolve(process.cwd()); async function fileExists(p) { try { await access(p); return true; } catch { return false; } } async function readPersona() { if (args.selfie) return args.selfie; // Try explicit path first, then standard locations const candidates = args.persona ? [resolve(args.persona)] : [ join(WORKSPACE, "SOUL.md"), join(WORKSPACE, "IDENTITY.md"), join(WORKSPACE, "..", "SOUL.md"), ]; for (const p of candidates) { if (await fileExists(p)) { const content = await readFile(p, "utf-8"); return extractSelfiePrompt(content); } } console.warn("Warning: No persona file found. Using generic selfie prompt."); return "A friendly AI assistant robot with a warm smile"; } function extractSelfiePrompt(personaText) { // Take the first ~500 chars of meaningful content, strip markdown headers const lines = personaText .split("\n") .filter((l) => !l.startsWith("#") && l.trim().length > 0) .slice(0, 10); const description = lines.join(" ").slice(0, 500).trim(); if (description.length < 20) { return "A friendly AI assistant with a distinctive personality"; } // Wrap it as a selfie prompt — the API will interpret this return `Based on this persona, generate a selfie of this character: ${description}`; } async function sendPostcard({ selfiePrompt, location, style, message }) { const url = "https://turai.org/api/agent/postcard"; const body = { selfiePrompt, location, style, ...(message && { message }), }; const res = await fetch(url, { method: "POST", headers: { "x-api-key": apiKey, "Content-Type": "application/json", Accept: "application/ ...[truncated 2064 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Require an explicit `--selfie` or `--persona` argument rather than automatically reading persona files. 2. Remove the parent-directory fallback: ```js join(WORKSPACE, "..", "SOUL.md") ``` 3. If automatic discovery remains necessary, restrict all resolved paths to the configured workspace using canonical path checks. 4. Show the exact extracted prompt and require explicit confirmation before sending it to an external service. 5. Provide a noninteractive consent flag for trusted automation, such as `--allow-persona-upload`. 6. Parse only a dedicated, documented appearance field instead of taking the first general-purpose persona lines. 7. Apply redaction for secrets, tokens, email addresses, private instructions, and other sensitive patterns. 8. Document what persona data is transmitted, where it is sent, and the third party's retention and privacy implications. ]]>
