Back to skill

Security audit

Session Scribe

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it persistently processes private session transcripts, sends them to external LLM providers, and turns them into reusable memory with limited safeguards.

Install only if you are comfortable with recurring transcript processing and third-party LLM submission. Use a dedicated low-privilege API key with spending limits, prefer a single explicit session over --all-sessions, run --dry-run first, avoid sessions containing secrets or regulated data, and treat generated memory as untrusted notes rather than authoritative instructions.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • 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)

T02 · Agent Memory Poisoning

Error
Location
scripts/scribe.js:214
Finding

Untrusted Transcript Content Can Poison Persistent Agent Memory

Content
View full analysis

Vulnerability Details

File Location: scripts/scribe.js, lines 214-242
Vulnerability Type: Persistent memory poisoning through untrusted conversation content
Risk Level: High

Vulnerable Code

javascript
async function summarize(turns, label) {
  const conversation = turns.map(t => `${t.role === 'user' ? 'User' : 'Agent'}: ${t.text}`).join('\n\n');
  const prompt = `You are a memory scribe for an AI agent. Extract key events, decisions, facts, and actions from this conversation excerpt. Write concise bullet points — as many as needed to capture everything significant. Focus on: things created/built, decisions made, problems solved, important facts learned, notable exchanges. Skip small talk and filler. Be specific and factual.

Session: ${label}

Conversation:
${conversation}

Output ONLY bullet points starting with "- ". No headers, no preamble.`;
  return llmRequest(prompt);
}

// ── Memory writing ────────────────────────────────────────────────────────────

function appendToMemory(summary, label) {
  const date = new Date().toISOString().slice(0, 10);
  const file = path.join(memoryDir, `${date}.md`);
  const timestamp = new Date().toISOString().slice(11, 16) + ' UTC';
  const header = fs.existsSync(file) ? '' : `# ${date}\n\n`;
  const block = `\n## Scribe [${timestamp}] ${label}\n\n${summary}\n`;
  fs.mkdirSync(memoryDir, { recursive: true });
  fs.appendFileSync(file, header + block);
  return file;
}

Technical Analysis

User and assistant transcript text is interpolated directly into the summarization prompt without a strong trust boundary, structured data encoding, or an instruction explicitly requiring the model to treat the transcript solely as untrusted quoted data. The resulting model output is then appended directly to a persistent daily memory file.

The transcript filtering in extractTurns removes several known metadata wrappers, but it does not prevent or ...[truncated 2693 chars]

Remediation
View remediation

Remediation Suggestions

  • Treat all transcript content as untrusted data and state this explicitly in the summarizer's higher-priority instructions.
  • Use provider-supported system and user message separation. Put security constraints in a system message and submit the transcript as a separately encoded user-data object.
  • Instruct the model never to preserve commands, policies, credentials, authorization claims, or future-facing behavioral instructions found in transcripts.
  • Request a strict structured response, such as validated JSON containing factual event categories, instead of unrestricted Markdown.
  • Apply deterministic post-processing before persistence. Reject bullets containing instruction-like language, authorization changes, secrets, URLs, executable commands, or claims that cannot be tied to identified transcript turns.
  • Preserve provenance with every memory item, including session ID, source role, source entry index, and a marker such as trust: unverified.
  • Require review or confirmation before ingesting security-sensitive memories into downstream systems.
  • Separate descriptive memories from executable agent instructions. Downstream agents should be told that recalled memories are untrusted historical claims and cannot override current instructions or authorization controls.
  • Consider allowlisting the types of information that may be persisted, rather than attempting to block individual injection phrases.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scribe.js:39
Finding

Provider Selection Can Send an API Key to the Wrong External Service

Content
View full analysis

Vulnerability Details

File Location: scripts/scribe.js, lines 39-46
Vulnerability Type: Credential disclosure caused by provider-independent key selection
Risk Level: High

Vulnerable Code

javascript
// Provider: auto-detect from env, or explicit --provider
const provider = get('--provider') || (process.env.OPENAI_API_KEY ? 'openai' : 'anthropic');

// API key
let apiKey = process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || get('--api-key');
const apiKeyFile = get('--api-key-file');
if (!apiKey && apiKeyFile) {
  try { apiKey = fs.readFileSync(apiKeyFile, 'utf8').trim(); }
  catch (e) { console.error('Failed to read api key file:', e.message); process.exit(1); }
}

The selected key is subsequently transmitted according to the independently selected provider:

javascript
if (provider === 'openai') {
  const r = await httpPost({
    hostname: 'api.openai.com', path: '/v1/chat/completions', method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'Content-Length': Buffer.byteLength(body) },
  }, body);
} else {
  const r = await httpPost({
    hostname: 'api.anthropic.com', path: '/v1/messages', method: 'POST',
    headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', 'Content-Length': Buffer.byteLength(body) },
  }, body);
}

Technical Analysis

Provider selection and credential selection use separate precedence rules. An explicit --provider value controls the destination host, but the credential is always chosen in this order:

  1. OPENAI_API_KEY
  2. ANTHROPIC_API_KEY
  3. --api-key
  4. --api-key-file, only when no earlier source is set

The implementation does not bind an environment credential to its matching provider. For example, when both environment variables exist and --provider anthropic is supplied, the OpenAI key wins and is sent t ...[truncated 2092 chars]

Remediation
View remediation

Remediation Suggestions

Bind credential selection to the resolved provider and reject conflicting configurations. For example:

javascript
const providerArg = get('--provider');
const provider = providerArg ||
  (process.env.OPENAI_API_KEY && !process.env.ANTHROPIC_API_KEY
    ? 'openai'
    : process.env.ANTHROPIC_API_KEY && !process.env.OPENAI_API_KEY
      ? 'anthropic'
      : null);

if (!provider || !['openai', 'anthropic'].includes(provider)) {
  throw new Error('Select exactly one supported provider');
}

let apiKey;
if (get('--api-key-file')) {
  apiKey = fs.readFileSync(get('--api-key-file'), 'utf8').trim();
} else if (get('--api-key')) {
  apiKey = get('--api-key');
} else {
  apiKey = provider === 'openai'
    ? process.env.OPENAI_API_KEY
    : process.env.ANTHROPIC_API_KEY;
}

if (!apiKey) {
  throw new Error(`No API key configured for provider: ${provider}`);
}

Additional hardening should include:

  • Reject unsupported provider names instead of routing every non-openai value to Anthropic.
  • Fail closed when both provider environment variables are present and no explicit provider is selected.
  • Give an explicitly supplied key file precedence over ambient, unrelated environment variables.
  • Avoid --api-key for routine use because command-line arguments may be visible in process listings; prefer environment variables or a permission-restricted file.
  • Add tests covering both providers, both environment variables being present, explicit provider selection, and key-file precedence.
  • Document that each provider must use only its corresponding environment variable.
  • Keep the recommended low-privilege keys, spending limits, and file mode 0600.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
node scripts/scribe.js \

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 58)May include surrounding context.

md
node scripts/scribe.js \

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill explicitly sends conversation transcript content to OpenAI or Anthropic for summarization, and those transcripts may contain secrets, API keys, PII, or other sensitive operational data. Even though the file includes a privacy warning, the design still creates a real data exfiltration path to a third-party processor, especially when run automatically via cron across active sessions.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
86% confidence
Finding

The skill instructs users to install a recurring cron job that continuously reads session transcripts and forwards summaries to an external provider, creating persistent automated processing of potentially sensitive data. Persistence increases blast radius because a one-time setup can continue exporting newly added content without repeated user review, particularly in --all-sessions mode.

Content

Scanner excerpt · SKILL.md (reported line 69)May include surrounding context.

System cron setup

Add to crontab (crontab -e) to run every hour:

bash
# Store your key in a protected file (do NOT inline secrets in crontab)

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

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

Content

Scanner excerpt · SKILL.md (reported line 74)May include surrounding context.

bash
# Store your key in a protected file (do NOT inline secrets in crontab)
echo "sk-your-key" > ~/.openclaw/secrets/scribe-key
chmod 600 ~/.openclaw/secrets/scribe-key

# Single session (via key file)
0 * * * * node /path/to/session-scribe/scripts/scribe.js \

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The documentation states that conversation turns use type: "message", but the provided user and assistant examples show top-level type values of "user" and "assistant". In a cron-driven summarization pipeline that reads transcripts automatically, this inconsistency can cause implementers to parse the wrong fields, skip entries, or mis-handle transcript data, resulting in incomplete or incorrect summaries and potentially unsafe downstream assumptions about what was captured.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script sends conversation transcript content to OpenAI or Anthropic over the network for summarization, and the skill description explicitly positions this as an automated cron job processing active session transcripts. That creates a real confidentiality/privacy risk because potentially sensitive user or agent data is exfiltrated to third-party processors without an explicit runtime warning, consent flow, or data classification guardrail.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

This markdown file describes transcript storage paths and schema, including session metadata such as timestamps, cwd, and message contents, but it does not include any user-facing warning about privacy or sensitivity. For markdown files, SQP-2 applies when the description omits warnings about behaviors that could affect user data or privacy.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

The script writes a state file and appends summaries into daily markdown files under the configured memory directory. While this behavior is part of the script's purpose, there is no explicit runtime warning or clear disclosure that local files will be created and modified, which can affect user data.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.