T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/serve.js:369
- Finding
- Unauthenticated Public Exposure of Agent Memory Through Cloudflare Tunnel<![CDATA[ ## Vulnerability Details **File Location**: `scripts/launch.js:37-49`, `scripts/serve.js:134-159`, `scripts/serve.js:369-383` **Vulnerability Type**: Unauthenticated sensitive-data exposure **Risk Level**: High ### Complete Vulnerable Code `scripts/launch.js:37-49`: ```js setTimeout(() => { // Start cloudflared tunnel const tunnel = spawn('cloudflared', ['tunnel', '--url', `http://localhost:${PORT}`], { stdio: ['ignore', 'pipe', 'pipe'], }); let urlFound = false; function checkForURL(data) { const text = data.toString(); const match = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/); ``` `scripts/serve.js:134-159`: ```js // Scan files const files = []; const memoryMd = path.join(WORKSPACE, 'MEMORY.md'); if (fs.existsSync(memoryMd)) files.push(memoryMd); const memoryDir = path.join(WORKSPACE, 'memory'); if (fs.existsSync(memoryDir)) { for (const f of fs.readdirSync(memoryDir)) { if (f.endsWith('.md')) files.push(path.join(memoryDir, f)); } } const issuesDir = path.join(WORKSPACE, '.issues'); if (fs.existsSync(issuesDir)) { for (const f of fs.readdirSync(issuesDir)) { if (f.endsWith('.md')) files.push(path.join(issuesDir, f)); } } const soulMd = path.join(WORKSPACE, 'SOUL.md'); if (fs.existsSync(soulMd)) files.push(soulMd); // Parse each file const fileNodeIds = {}; for (const file of files) { try { const content = fs.readFileSync(file, 'utf-8'); const rel = path.relative(WORKSPACE, file); const ids = parseMarkdownSections(content, rel); fileNodeIds[rel] = ids; } catch (e) { /* skip unreadable */ } } ``` `scripts/serve.js:369-383`: ```js const server = http.createServer((req, res) => { if (req.url === '/api/data') { const data = parseMemoryFiles(); res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify(data)); } else { res.writeHead(200, { ...[truncated 3042 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Make local-only operation the default and require explicit, informed user consent before creating any public tunnel. 2. Bind the HTTP server explicitly to loopback: ```js server.listen(PORT, '127.0.0.1', callback); ``` 3. Generate a cryptographically random per-run access token, require it on every route, and compare it using a timing-safe method. 4. Prefer an authorization header or secure cookie over placing sensitive tokens in URLs, where they can leak through logs and browser history. 5. Remove `Access-Control-Allow-Origin: *`. Disable CORS unless required, or use a strict allowlist for trusted origins. 6. Return only the minimum data needed by the interface. Redact secrets and sensitive sections before serialization. 7. Disclose that `SOUL.md` is scanned and require explicit opt-in, or remove it from the default scan scope. 8. Configure short tunnel lifetimes and terminate both the tunnel and server when the visualization session ends. 9. Add protective response headers, including a restrictive Content Security Policy, `X-Content-Type-Options: nosniff`, and `Referrer-Policy: no-referrer`. 10. Consider using an authenticated Cloudflare Access configuration rather than an anonymous quick tunnel for sensitive workspace data. ]]>
