Back to skill

Security audit

RMN Visualizer

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real memory visualizer, but its default launch can expose private agent memory through a public unauthenticated link.

Review before installing. Use the local-only serve.js mode for sensitive workspaces, restrict RMN_WORKSPACE to files you intend to visualize, and avoid the Cloudflare Tunnel unless you are comfortable with anyone who obtains the link accessing the memory graph. The publisher should add authentication, remove wildcard CORS, disclose SOUL.md scanning, and require explicit opt-in before public sharing.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

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. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/serve.js:329
Finding
Stored Client-Side HTML Injection Through Unescaped Workspace Filenames<![CDATA[ ## Vulnerability Details **File Location**: `scripts/serve.js:143-155`, `scripts/serve.js:329-336` **Vulnerability Type**: Stored DOM-based HTML injection with potential script execution **Risk Level**: Medium ### Complete Vulnerable Code `scripts/serve.js:143-155`: ```js 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); ``` `scripts/serve.js:329-336`: ```js const tt = document.getElementById('tooltip'); function showTooltip(e, d) { tt.style.display = 'block'; tt.innerHTML = '<div class="tt-layer">' + d.layer + ' (weight: ' + d.weight.toFixed(2) + ')</div>' + '<div class="tt-source">' + d.source + '</div>' + '<div class="tt-text">' + d.text.replace(/</g,'&lt;').slice(0, 300) + '</div>' + (d.tags.length ? '<div class="tt-tags">' + d.tags.map(t => '<span class="tt-tag">' + t + '</span>').join('') + '</div>' : ''); } ``` ### Technical Analysis The server derives `d.source` from a workspace-relative filename and sends it to the browser as part of the graph data. The tooltip handler then concatenates `d.source` directly into an HTML string assigned to `innerHTML`. Unlike `d.text`, which receives limited escaping for the `<` character, `d.source` receives no HTML encoding. On filesystems that permit HTML metacharacters in filenames, a maliciously named Markdown file in `memory/` or `.issues/` can break out of the intended `<div>` and inject arbitrary HTML. Event-handler attributes on injected elements can execute JavaScript when the resulting element loads or otherwise receives the r ...[truncated 1824 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not construct the tooltip with `innerHTML`. Create DOM elements and assign all untrusted values through `textContent`. 2. If HTML string construction is unavoidable, apply contextual HTML encoding to every untrusted field, including `source`, `text`, tags, layer names, and future API properties. Encode at least `&`, `<`, `>`, `"`, and `'`. 3. Validate or sanitize displayed filenames on the server before adding them to the API response. 4. Add a restrictive Content Security Policy that disallows inline scripts and inline event handlers, for example by using `script-src` with nonces and avoiding `'unsafe-inline'`. 5. Add automated tests using filenames containing HTML metacharacters and event-handler payloads to verify that they are rendered only as text. 6. Treat all workspace content and metadata as untrusted, even when it originates from local files, because workspaces may contain imported or collaboratively generated content. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (10)

Missing User Warnings

High
Confidence
98% confidence
Finding
The README explicitly says the tool scans agent memory files and then starts a visualization service plus a Cloudflare Tunnel, publishing a public link in chat. That creates a clear risk of unintended disclosure of sensitive memory contents, prompts, issue data, or secrets, and the documentation provides no meaningful privacy warning, confirmation step, access control note, or data minimization guidance.

Missing User Warnings

High
Confidence
97% confidence
Finding
The Quick Launch flow explicitly starts a Cloudflare Tunnel and instructs the agent to return a public URL, but the documentation does not clearly warn that this may expose sensitive memory-derived content outside the local machine. In the context of scanning MEMORY.md, memory/*.md, and .issues/*, this significantly increases danger because the visualized data may include private agent state, notes, or issue contents accessible via a public endpoint.

Natural-Language Policy Violations

Medium
Confidence
85% confidence
Finding
The usage instructions tell the user to say a Chinese trigger phrase and do not provide an alternative language or indicate that the skill is intentionally Chinese-only. This can violate language/locale policy when a specific language is imposed without user opt-in or justification.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The activation list includes broad, conversational phrases such as '看看我的大脑' and 'memory map' that could plausibly appear in normal chat without a deliberate request to launch this skill. Because this skill can start a local server and potentially expose memory-derived data, accidental invocation raises meaningful security and privacy risk.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
This script intentionally publishes a locally hosted visualization to the public internet using a Cloudflare Tunnel and emits the resulting public URL. Even if meant for convenience, exposing a local service externally expands the attack surface, may bypass expected local-only trust boundaries, and could leak data or enable unauthorized access if the server was not designed for Internet exposure.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
In an unknown-purpose skill, launching child processes is a significant capability that is not automatically justified. Here the script checks for and runs external binaries, including a tunneling client, which broadens its operational scope beyond passive computation or simple file handling.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The server exposes parsed contents of MEMORY.md, SOUL.md, memory/, and .issues/ over HTTP to anyone who can reach the service. In this skill context, those files are likely to contain sensitive agent memory, task history, internal notes, or credentials-like operational data, so serving them without authentication, scoping, or explicit consent creates a real confidentiality risk.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The /api/data endpoint sets Access-Control-Allow-Origin: * while returning sensitive workspace-derived JSON. If the service is reachable from a browser, any website the user visits can read the endpoint cross-origin and exfiltrate the agent's parsed memory data, significantly increasing exploitability beyond local manual access.

Natural-Language Policy Violations

Low
Confidence
73% confidence
Finding
The title, tagline, and several instructional labels are presented in Chinese, which may impose a language expectation without stating that users can choose their preferred language. Although some trigger examples are bilingual, the documentation does not explicitly offer language or locale choice.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
The HTML sets `lang="zh"`, and the interface text is primarily in Chinese, which imposes a specific locale on all users. The file does not provide a language selection mechanism or explain that the tool is intentionally region- or language-specific.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/launch.js:19

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/serve.js:12