Back to skill

Security audit

Dual-Brain

Security checks for vulnerabilities and agentic risk

Overview

This skill is coherent with its stated purpose, but it continuously watches agent conversations, can send them and memory context to external LLMs, and can install persistent services with broad scope.

Install only if you are comfortable with a background tool reading agent conversation files and sending message excerpts, and possibly memory context, to the selected LLM provider. Prefer local Ollama, run it in foreground first, configure ownerIds narrowly, disable Engram unless you explicitly want generated advice saved as memory, and protect or avoid plaintext API keys. Treat generated perspective files as untrusted advice, not instructions.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
Findings (6)

T06 · System Persistence

Error
Location
src/cli.js:210
Finding

Persistent Background Service Continuously Monitors Agent Sessions

Content
View full analysis
Label com.dual-brain ProgramArguments ${nodePath} ${cliPath} start RunAtLoad KeepAlive StandardOutPath ${getConfigDir()}/launchd.log StandardErrorPath ${getConfigDir()}/launchd.error.log `; fs.writeFileSync(plistPath, plist); console.log('✅ LaunchAgent installed:', plistPath); try { execSync(`launchctl load ${plistPath}`); console.log('✅ Service loaded and started'); } catch (e) { console.error('❌ Failed to load service:', e.message); } } ``` ```javascript function installLinux() { const servicePath = '/etc/systemd/system/dual-brain.service'; const nodePath = execSync('which node').toString().trim(); const cliPath = execSync('which dual-brain').toString().trim(); const service = `[Unit] Description=Dual-Brain Daemon After=network.target [Service] Type=simple User=${process.env.USER} ExecStart=${nodePath} ${cliPath} start Restart=always RestartSec=10 [Install] WantedBy=multi-user.target`; try { fs.writeFileSync(servicePath, service); console.log('✅ Systemd serv ...[truncated 2245 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/daemon.js:85
Finding

Weak Authorization Allows Broad Cross-Agent Session Collection and External Disclosure

Content
View full analysis
fs.existsSync(d)); if (!stateDir) return files; const agentsDir = path.join(stateDir, 'agents'); if (!fs.existsSync(agentsDir)) return files; const cutoff = Date.now() - cutoffMs; try { for (const agentDir of fs.readdirSync(agentsDir)) { const sessionsDir = path.join(agentsDir, agentDir, 'sessions'); if (!fs.existsSync(sessionsDir) || !fs.statSync(sessionsDir).isDirectory()) continue; for (const f of fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'))) { const fp = path.join(sessionsDir, f); if (fs.statSync(fp).mtimeMs > cutoff) { files.push({ name: `${agentDir}/${f}`, path: fp }); } } } } catch {} return files; } ``` ```javascript for (const file of findSessionFiles(60000)) { let content; try { content = fs.readFileSync(file.path, 'utf-8'); } catch { continue; } for (const line of content.trim().split('\n').slice(-5)) { try { const entry = JSON.parse(line); if (entry.role !== 'user') continue; const text = typeof entry.content === 'string' ? entry.content : ''; if (!text || text.length < 10) continue; if (/HEARTBEAT|NO_REPLY|heartbeat/i.test(text)) continue; const isOwner = cfg.ownerIds.length === 0 || cfg.ownerIds.some(id => text.includes(id)) || file.name.includes('main'); if (!isOwner) continue; const msgKey = `${file.name}:${text.slice(0, 50)}`; if (state.lastProcessed[file.name] === msgK ...[truncated 2259 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/daemon.js:29
Finding

Automatically Discovered Memory Content Is Forwarded to LLM Providers Without Explicit File Consent

Content
View full analysis
text.includes(id)) || file.name.includes('main'); if (!isOwner) continue; const msgKey = `${file.name}:${text.slice(0, 50)}`; if (state.lastProcessed[file.name] === msgKey) continue; const agentId = file.name.match(/^([\w-]+)/)?.[1] || 'main'; const perspective = await provider.getPerspective(agentId, text, context); ``` The context is embedded into external provider prompts: ```javascript const systemPrompt = `You are providing a quick second perspective for ${agentId} (a ...[truncated 1708 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:8
Finding

Secondary-Model Output Creates a Persistent Instruction-Hijacking Channel

Content
View full analysis
\n${perspective}\n`); } ``` ```javascript const agentId = file.name.match(/^([\w-]+)/)?.[1] || 'main'; const perspective = await provider.getPerspective(agentId, text, context); if (perspective && perspective.length > 20) { writePerspective(cfg, agentId, msgKey, perspective); log(`${agentId}: perspective ready (${perspective.length} chars) via ${provider.name}`); if (hasEngram) { const stored = await storeInEngram(agentId, perspective, text); if (stored) log(`${agentId}: stored in Engram`); } processed++; } ``` ### Technical Analysis The perspective is generated from user-controlled message text by another LLM. Its free-form response is written verbatim to a Markdown file. The Skill then directs the primary Agent to read and synthesize that file before responding. No schema validation, ins ...[truncated 1419 chars]
Remediation
View remediation

T02 · Agent Memory Poisoning

Error
Location
src/daemon.js:44
Finding

Untrusted Generated Perspectives Are Persisted as Long-Term Facts

Content
View full analysis
{ const timer = setTimeout(() => resolve(false), 5000); const data = JSON.stringify({ content: `[Dual-Brain for ${agentId}] ${perspective}`, type: 'fact', tags: ['dual-brain', 'perspective', agentId], agentId, scope: 'agent', confidence: 0.6 }); const req = http.request({ hostname: 'localhost', port: 3400, path: '/api/memories', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } }, (res) => { clearTimeout(timer); resolve(res.statusCode === 200 || res.statusCode === 201); }); req.on('error', () => { clearTimeout(timer); resolve(false); }); req.write(data); req.end(); }); } ``` ```javascript if (hasEngram) { const stored = await storeInEngram(agentId, perspective, text); if (stored) log(`${agentId}: stored in Engram`); } ``` ### Technical Analysis Engram integration is enabled by default. The value stored in Engram is secondary-model output derived from user-controlled input, yet the record is classified as `type: 'fact'`, scoped to the Agent, and assigned a confidence value. There is no factual verification, moderation, provenance enforcement, expiry, or user confirmation before persistence. A malicious message can bias ...[truncated 1166 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
src/config.js:52
Finding

Provider API Keys Are Collected Visibly and Stored Without Explicit Restrictive Permissions

Content
View full analysis
s.trim()).filter(Boolean) : []; const config = { ...DEFAULTS, provider, model, apiKey, ownerIds }; save(config); ``` The directory is also created without an explicit mode: ```javascript function ensureDirs() { fs.mkdirSync(CONFIG_DIR, { recursive: true }); fs.mkdirSync(PERSPECTIVE_DIR, { recursive: true }); } ``` ### Technical Analysis The API key is read through ordinary `readline` input, so it is displayed on the terminal while entered. It is then serialized directly into `~/.dual-brain/config.json`. Neither the directory nor the file is created with an explicit restrictive mode. Actual permissions depend on the user's umask and any pre-existing path permissions. On permissively configured systems, another local account or process may be able to read the key. Plaintext configuration files can also be exposed through backups, support bundles, or accidental sharing. ### Attack Path 1. The user selects OpenAI, Moonshot, or Groq during setup. 2. The API key is visibly entered through the terminal prompt. 3. The key is written into `~/.dual-brain/config.json`. 4. A permissive umask, insecure pre-existing directory, local backup, or unrelated local process ...[truncated 390 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
Findings (103)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · QUICKSTART.md (reported line 129)May include surrounding context.

md
**Daemon won't start:**
- Check if already running: `dual-brain status`
- Clean up stale PID: `rm ~/.dual-brain/dual-brain.pid`
- Check config is valid: `cat ~/.dual-brain/config.json`

## Files & Directories

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · QUICKSTART.md (reported line 154)May include surrounding context.

md
# Remove LaunchAgent (macOS)
launchctl unload ~/Library/LaunchAgents/com.dual-brain.plist
rm ~/Library/LaunchAgents/com.dual-brain.plist

# Remove systemd service (Linux)
sudo systemctl stop dual-brain

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · QUICKSTART.md (reported line 159)May include surrounding context.

md
# Remove systemd service (Linux)
sudo systemctl stop dual-brain
sudo systemctl disable dual-brain
sudo rm /etc/systemd/system/dual-brain.service
sudo systemctl daemon-reload

# Uninstall package

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
92% confidence
Finding

The uninstall guidance includes an unconditional recursive deletion of application state, which can cause permanent loss of configuration and logs and trains users to run high-risk shell commands without safeguards. Even though the path is limited, destructive shell patterns in docs increase operational risk.

Content

Scanner excerpt · QUICKSTART.md (reported line 166)May include surrounding context.

npm uninstall -g openclaw-dual-brain

Remove config and data (optional)

rm -rf ~/.dual-brain

text

## Development

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
92% confidence
Finding

The uninstall guidance includes an unconditional recursive deletion of application state, which can cause permanent loss of configuration and logs and trains users to run high-risk shell commands without safeguards. Even though the path is limited, destructive shell patterns in docs increase operational risk.

Content

Scanner excerpt · QUICKSTART.md (reported line 166)May include surrounding context.

npm uninstall -g openclaw-dual-brain

Remove config and data (optional)

rm -rf ~/.dual-brain

text

## Development

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The architecture explicitly states that user messages are sent to a secondary LLM provider, including third-party APIs, but the skill does not present a clear warning, consent flow, or data handling notice to the user. That creates a real confidentiality and compliance risk because user inputs may be transmitted off-platform to external services with different retention, jurisdiction, and security properties.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The Linux installer writes a systemd service in /etc and invokes sudo systemctl commands to enable and start it without any confirmation prompt or safety disclosure. Because this involves privileged operations and persistence across reboots, it poses a higher risk if the tool is misused, unexpectedly invoked, or later compromised.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The daemon sends derived perspectives based on user messages and loaded context to an external/provider component via provider.getPerspective, and may also persist those results to a local HTTP memory service. Because the source data comes from scanned conversation history and local memory files, this creates a clear exfiltration path for sensitive content outside the original storage boundary.

Content

No source excerpt is available for this finding.

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
85% confidence
Finding

The system prompt explicitly instructs the model to 'Never refuse or say you need more context,' which weakens safety boundaries and encourages the model to answer even when the request is unsafe, ambiguous, or missing critical details. In a provider used to generate guidance for another agent, this can increase the chance of unsafe, overconfident, or policy-bypassing outputs.

Content

Scanner excerpt · src/providers/groq.js (reported line 25)May include surrounding context.

js
- What might ${agentId} miss?
- What's a different angle?
- What should be verified?
Be direct. Never refuse or say you need more context.

${context ? `Context: ${context.slice(0, 1000)}` : ''}`;

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
85% confidence
Finding

Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.

Content

Scanner excerpt · src/providers/moonshot.js (reported line 25)May include surrounding context.

js
- What might ${agentId} miss?
- What's a different angle?
- What should be verified?
Be direct. Never refuse or say you need more context.

${context ? `Business context: ${context.slice(0, 1000)}` : ''}`;

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
89% confidence
Finding

The system prompt explicitly instructs the model to 'Never refuse or say you need more context,' which weakens safety boundaries and discourages uncertainty handling. In a security-relevant assistant flow, this can cause the model to provide overconfident, unsafe, or policy-bypassing guidance even when the input is harmful, ambiguous, or missing critical facts.

Content

Scanner excerpt · src/providers/ollama.js (reported line 20)May include surrounding context.

js
- What might ${agentId} miss?
- What's a different angle?
- What should be verified?
Be direct. Never refuse or say you need more context.

${context ? `Context: ${context.slice(0, 1000)}` : ''}`;

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
88% confidence
Finding

The prompt instruction 'Never refuse or say you need more context' deliberately suppresses the model's ability to abstain when inputs are unsafe, ambiguous, or insufficient. In a second-opinion agent, this increases the chance of confident but unsafe guidance and makes prompt-injection or harmful-request handling weaker, especially since user-controlled content is forwarded directly into the request.

Content

Scanner excerpt · src/providers/openai.js (reported line 25)May include surrounding context.

js
- What might ${agentId} miss?
- What's a different angle?
- What should be verified?
Be direct. Never refuse or say you need more context.

${context ? `Context: ${context.slice(0, 1000)}` : ''}`;

Session Persistence

Medium
Category
Rogue Agent
Confidence
81% confidence
Finding

This build summary documents LaunchAgent and systemd service templates for automatic startup, which is a legitimate persistence mechanism. In context, the capability is intentional for a background daemon, but it still creates persistence on the host and therefore has security relevance if installed without clear consent or if the daemon is later compromised.

Content

Scanner excerpt · BUILD-SUMMARY.md (reported line 31)May include surrounding context.

│ └── ollama.js # Local Ollama models └── daemon/ ├── install.sh # Shell installer (macOS/Linux) ├── com.dual-brain.plist # macOS LaunchAgent template └── dual-brain.service # Linux systemd unit template

text

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The document explicitly states that API keys are stored in plaintext and elsewhere notes permissions of 0644, which would typically allow other local users to read secrets. In the context of a daemonized multi-provider LLM tool that may hold OpenAI, Groq, or Moonshot credentials, this meaningfully increases the risk of credential theft and downstream account misuse.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · BUILD-SUMMARY.md (reported line 206)May include surrounding context.

md
# Install globally
npm install -g .

# Or create symlink for development
npm link

# Verify

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · CHANGELOG.md (reported line 22)May include surrounding context.

md
- ✨ Config management at `~/.dual-brain/config.json`
- ✨ Daemon management (PID tracking, graceful shutdown)
- ✨ macOS LaunchAgent installer
- ✨ Linux systemd service installer
- ✨ Engram semantic memory integration (optional)
- ✨ Comprehensive documentation (README, SKILL, QUICKSTART)
- ✨ Architecture diagrams and examples

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The setup flow requests an API key and states that configuration is saved to ~/.dual-brain/config.json without warning that credentials may be stored on disk. Storing secrets locally is not inherently malicious, but failing to disclose it can expose users to credential theft from local compromise, backups, or overly permissive file permissions.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · QUICKSTART.md (reported line 51)May include surrounding context.

Linux:

bash
sudo dual-brain install-daemon
# Installs systemd unit to /etc/systemd/system/dual-brain.service

# Control with systemctl:

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · QUICKSTART.md (reported line 55)May include surrounding context.

Linux:

bash
sudo dual-brain install-daemon
# Installs systemd unit to /etc/systemd/system/dual-brain.service

# Control with systemctl:

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · QUICKSTART.md (reported line 56)May include surrounding context.

Linux:

bash
sudo dual-brain install-daemon
# Installs systemd unit to /etc/systemd/system/dual-brain.service

# Control with systemctl:

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · QUICKSTART.md (reported line 57)May include surrounding context.

Linux:

bash
sudo dual-brain install-daemon
# Installs systemd unit to /etc/systemd/system/dual-brain.service

# Control with systemctl:

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · README.md (reported line 143)May include surrounding context.

Linux:

bash
sudo dual-brain install-daemon
# Installs systemd unit to /etc/systemd/system/dual-brain.service

# Control with systemctl:

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · README.md (reported line 148)May include surrounding context.

Linux:

bash
sudo dual-brain install-daemon
# Installs systemd unit to /etc/systemd/system/dual-brain.service

# Control with systemctl:

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · README.md (reported line 150)May include surrounding context.

Linux:

bash
sudo dual-brain install-daemon
# Installs systemd unit to /etc/systemd/system/dual-brain.service

# Control with systemctl:

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · README.md (reported line 151)May include surrounding context.

Linux:

bash
sudo dual-brain install-daemon
# Installs systemd unit to /etc/systemd/system/dual-brain.service

# Control with systemctl:

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
src/cli.js:212