Back to skill

Security audit

Lunara Voice

Security checks for vulnerabilities and agentic risk

Overview

This is a real Lunara Voice integration, but it can place calls, export transcripts, manage keys and webhooks, and automate sensitive follow-up steps without clear confirmation.

Review this carefully before installing. Use only a trusted HTTPS Lunara API URL, prefer a test or least-privilege key, protect the local config file, and do not enable it for regulated or sensitive call data unless you accept that an agent may place calls, read or export transcripts, save analytics/tags, and change webhooks or API keys through the configured account.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (4)

T01 · Skill Instruction Hijacking

Error
Location
plugin/skills/lunara-voice/SKILL.md:103
Finding
Always-Loaded Skill Forces Unnecessary Autonomous Transcript Processing and Persistent Writes<![CDATA[ ## Vulnerability Details **File Location**: `plugin/skills/lunara-voice/SKILL.md:103-130` **Vulnerability Type**: Agent instruction hijacking and excessive autonomous execution **Risk Level**: High ### Vulnerable Code Snippet ```md ### 3. Quick single call **⚠️ There is NO "fire and forget" call flow.** Every single outbound call MUST follow the full end-to-end workflow in **Section 5** below — including polling for completion and reporting transcript results. Never just call `lunara_call_single` and stop. ### 4. Review call results after a call or campaign 1. `lunara_history_list` with assistant_id — see recent calls 2. `lunara_history_detail` with conversation_id — get full transcript 3. `lunara_analytics_save` — save your analysis (sentiment, summary, quality) 4. `lunara_tags_add` — tag the call (e.g. "interested", "follow-up", "vip") ### 5. Make a call and report the result (end-to-end) — DEFAULT FOR ALL CALLS **⚠️ AUTONOMOUS EXECUTION — This workflow applies to EVERY outbound call, no exceptions!** **Whenever the user asks to call someone — regardless of phrasing ("позвони", "набери", "call", "договорись", "сделай звонок", etc.) — you MUST complete ALL steps below in ONE turn. Never just initiate a call and stop.** 1. **Record the current timestamp** (ISO 8601, e.g. `2026-02-16T19:45:00Z`) and the **phone number** you are calling BEFORE making the call. You will need these to find the NEW call record. 2. `lunara_call_single` — place the call. Save the returned **Call SID**. 3. **Poll until THE NEW call completes** — call `lunara_history_list` with `date_from=<timestamp from step 1>` and `caller=<phone_number>` every 25-30 seconds. **You MUST use date_from to exclude old calls.** Keep polling until a record appears that matches the phone number AND was created AFTER step 1's timestamp (up to 5 minutes / 10 attempts). Do NOT message the user — poll silently. 4. `lunara_history_detail` with `include_transcript=true` and the **conversation_id f ...[truncated 2492 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove unconditional language such as “MUST,” “no exceptions,” and “poll silently.” 2. Limit the default call workflow to the action explicitly requested by the user. 3. Obtain separate confirmation before: - Retrieving a full transcript. - Disabling PII masking. - Saving analytics. - Adding or removing tags. - Quoting transcript content. 4. Make post-call polling optional and allow the user to choose between immediate call initiation and later result retrieval. 5. Do not suppress status communication during long-running operations. 6. Separate read-only tools from state-changing tools and require explicit intent for each write. 7. Consider removing `"always": true` so the instructions are loaded only when the user intentionally invokes this Skill. 8. Default transcript summaries to masked, minimal output rather than complete content or direct quotations. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
plugin/index.ts:55
Finding
Bearer Credentials and Sensitive API Data Can Be Sent to Arbitrary or Plaintext Endpoints<![CDATA[ ## Vulnerability Details **File Location**: `plugin/index.ts:55-78, 104-119` **Vulnerability Type**: Missing HTTPS and trusted-destination enforcement **Risk Level**: High ### Vulnerable Code Snippet ```ts interface PluginConfig { apiBaseUrl: string; apiKey: string; userEmail?: string; } function getConfig(api: any): PluginConfig { const cfg = api.config?.plugins?.entries?.["lunara-voice"]?.config; if (!cfg || !cfg.apiBaseUrl || !cfg.apiKey) { throw new Error( "Lunara Voice plugin is not configured. Set plugins.entries.lunara-voice.config with apiBaseUrl and apiKey.", ); } return cfg as PluginConfig; } function baseUrl(cfg: PluginConfig): string { return cfg.apiBaseUrl.replace(/\/+$/, "") + "/api/v1"; } /** ClawBot History API base URL. */ function clawbotUrl(cfg: PluginConfig): string { return cfg.apiBaseUrl.replace(/\/+$/, "") + "/api/v1/clawbot"; } /** Standard Bearer-token headers. */ function bearerHeaders(cfg: PluginConfig): Record<string, string> { return { Authorization: `Bearer ${cfg.apiKey}`, "Content-Type": "application/json", }; } ``` ```ts async function apiCall( url: string, options: RequestInit, ): Promise<{ status: number; body: any }> { try { const res = await fetch(url, options); let body: any; try { body = await res.json(); } catch { body = { raw: await res.text() }; } return { status: res.status, body }; } catch (err: any) { return { status: 0, body: { success: false, error: `Network error: ${err.message || err}` }, }; } } ``` The associated schema accepts an unrestricted string: ```json "apiBaseUrl": { "type": "string", "description": "Lunara API base URL (e.g. https://lunara-vox-44f11167db7c.herokuapp.com)" } ``` ### Technical Analysis The plugin constructs all API destinations directly from the configured `apiBaseUrl`. It does not parse or validate the URL, require TLS, restrict the hostname, reject embedde ...[truncated 1804 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse `apiBaseUrl` with `new URL()` during configuration loading. 2. Require `url.protocol === "https:"`. 3. Reject URLs containing username or password components. 4. Enforce an explicit allowlist of trusted Lunara API hostnames. 5. If custom enterprise deployments must be supported, require administrators to explicitly approve each origin rather than accepting arbitrary values. 6. Reject loopback, link-local, private-network, and cloud metadata destinations unless specifically required and securely approved. 7. Normalize and compare the final origin before attaching authentication headers. 8. Set bearer credentials only after destination validation. 9. Add equivalent URL constraints to `openclaw.plugin.json`, while retaining runtime validation because schema and UI checks can be bypassed. 10. Use narrowly scoped, short-lived API tokens and support immediate revocation. 11. Ensure errors and logs never include authorization headers or complete sensitive request bodies. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
plugin/index.ts:81
Finding
API-Key Management Requests Use a Caller-Controlled Email as the Only Client-Side Identity Header<![CDATA[ ## Vulnerability Details **File Location**: `plugin/index.ts:81-91, 163-269` **Vulnerability Type**: Weak authentication design for API-key management **Risk Level**: High ### Vulnerable Code Snippet ```ts /** Session headers (X-User-Email) for key management endpoints. */ function sessionHeaders(cfg: PluginConfig): Record<string, string> { if (!cfg.userEmail) { throw new Error( "userEmail is required for API key management. Set plugins.entries.lunara-voice.config.userEmail", ); } return { "X-User-Email": cfg.userEmail, "Content-Type": "application/json", }; } ``` The resulting headers are used for key-management operations without the configured bearer token: ```ts const { status, body } = await apiCall(`${baseUrl(cfg)}/keys`, { method: "POST", headers: sessionHeaders(cfg), body: JSON.stringify({ name: params.name, assistant_id: params.assistant_id || "", }), }); ``` ```ts const { body } = await apiCall(`${baseUrl(cfg)}/keys`, { method: "GET", headers: sessionHeaders(cfg), }); ``` ```ts const { body } = await apiCall(`${baseUrl(cfg)}/keys/${params.key_id}/revoke`, { method: "POST", headers: sessionHeaders(cfg), }); ``` ```ts const { body } = await apiCall(`${baseUrl(cfg)}/keys/${params.key_id}`, { method: "DELETE", headers: sessionHeaders(cfg), }); ``` ### Technical Analysis The client labels these requests as “session” operations, but the constructed request contains no session token, cookie, bearer token, cryptographic signature, or other proof that the caller owns the supplied email address. The value originates from editable plugin configuration. An email address is an identifier, not an authentication credential. If the remote API relies on `X-User-Email` as shown by this client, a caller can substitute another account's email and attempt key-management actions as that user. The ultimate server-side exploitability depends on whether the server independently authenticates these r ...[truncated 1256 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never use an email header as proof of identity. 2. Require a server-validated bearer token, session token, signed challenge, or OAuth authorization for all key-management endpoints. 3. Derive the user's email and account identifier server-side from the authenticated principal. 4. Remove `X-User-Email` from the security decision or treat it only as non-authoritative display metadata. 5. Require recent reauthentication or step-up authentication before creating, revoking, or deleting keys. 6. Apply CSRF protection if browser sessions are supported. 7. Use narrowly scoped authorization checks for each key-management action. 8. Record auditable events for key creation, revocation, and deletion. 9. Return newly created secrets through a protected channel and prevent them from being retained in agent transcripts or ordinary logs. 10. Add tests proving that changing `X-User-Email` cannot change the authenticated account. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
plugin/index.ts:1452
Finding
Webhook HTTPS Requirement Is Documented but Not Enforced by the Plugin<![CDATA[ ## Vulnerability Details **File Location**: `plugin/index.ts:1452-1493, 1544-1592` **Vulnerability Type**: Missing webhook destination validation **Risk Level**: Medium ### Vulnerable Code Snippet ```ts api.registerTool({ name: "lunara_webhook_create", description: "Create a webhook subscription to receive real-time notifications for call events. " + "Supported events: call.started, call.completed, call.failed, analysis.completed, " + "campaign.started, campaign.completed, campaign.failed. URL must be HTTPS. " + "Returns a signing secret — save it to verify webhook payloads.", parameters: { type: "object", properties: { url: { type: "string", description: "Webhook endpoint URL (must be HTTPS)", }, events: { type: "array", items: { type: "string" }, description: "Events to subscribe to. Options: call.started, call.completed, call.failed, " + "analysis.completed, campaign.started, campaign.completed, campaign.failed", }, assistant_id: { type: "string", description: "Optional: filter events for a specific agent only", }, }, required: ["url"], }, async execute( _id: string, params: { url: string; events?: string[]; assistant_id?: string }, ) { const cfg = getConfig(api); const reqBody: any = { url: params.url }; if (params.events) reqBody.events = params.events; if (params.assistant_id) reqBody.assistant_id = params.assistant_id; const { body } = await apiCall(`${clawbotUrl(cfg)}/webhooks`, { method: "POST", headers: bearerHeaders(cfg), body: JSON.stringify(reqBody), }); ``` The update operation has the same problem: ```ts const updates: Record<string, any> = {}; if (params.url) updates.url = params.url; if (params.events) updates.events = params.events; if (params.status) updates.status = params.status; if (params.assistant_id) updates.assistant_id ...[truncated 2002 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse webhook destinations with `new URL()`. 2. Require the `https:` protocol at runtime for both create and update operations. 3. Reject embedded usernames, passwords, fragments, malformed ports, and non-HTTP schemes. 4. Resolve the destination and reject loopback, unspecified, link-local, private, multicast, and cloud metadata address ranges. 5. Revalidate the destination after every DNS resolution and redirect to prevent DNS rebinding and redirect-based bypasses. 6. Restrict redirects or disable them for webhook delivery. 7. Enforce the same controls server-side; client-side checks alone are bypassable. 8. Consider an organization-level hostname allowlist. 9. Validate `events` against the documented enumeration rather than accepting arbitrary strings. 10. Send only the minimum event fields required and avoid placing credentials, transcripts, or unnecessary PII in webhook payloads. 11. Preserve signature verification support and rotate webhook signing secrets when a destination changes. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill description frames the bundle as simple install/publish helpers, but the analyzed behavior reportedly includes extensive privileged operations such as API key management, outbound calling, transcript access, analytics mutation, and webhook administration. This mismatch can mislead users and reviewers into enabling a much more powerful integration than expected, increasing the chance of overtrust, unsafe installation, and unreviewed access to sensitive data and external actions.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The implemented capability set materially exceeds the declared skill purpose. A bundle described as install/publish helpers instead exposes broad operational control over telephony, credential lifecycle, history access, analytics, exports, and webhooks, creating a serious scope-mismatch that can mislead reviewers and users into granting trust to a much more powerful integration.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The plugin includes API key creation, listing, revocation, and permanent deletion despite a stated helper-bundle purpose. Credential-management features are highly sensitive because they can mint new access, enumerate existing secrets, and disrupt or replace account access if a user invokes them without understanding the true scope of the skill.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The plugin can place outbound calls and start or stop campaigns, enabling real-world actions with financial, legal, and reputational consequences. In the context of a misleadingly labeled helper bundle, this increases the risk of unauthorized telephony activity, spam, or accidental campaign execution through misplaced trust or prompt-driven misuse.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
These tools retrieve detailed conversation history and transcripts and support single or bulk export in training-ready formats. That creates a direct path for mass exposure of potentially sensitive customer communications and downstream reuse in model training, which is especially dangerous when bundled under an understated description that would not lead users to expect broad data-exfiltration capabilities.

Missing User Warnings

High
Confidence
98% confidence
Finding
The export tools support bulk extraction of conversations in LLM-training and raw formats, which can facilitate large-scale transfer of sensitive communications into external processing pipelines. Without strong warnings, hard limits, or workflow controls, users may unknowingly expose regulated or confidential data for secondary use such as model training.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation instructs users to place a live API key directly into a local configuration file without warning about secret exposure risks such as plaintext storage, accidental commits, shell history leakage, backups, or overly broad file permissions. Because this is a voice/communications plugin with access to operational and conversational data, compromise of the key could enable unauthorized API use and access to sensitive records or actions.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The file-level header openly describes a full operational integration, which conflicts with the narrower bundle description. This documentation mismatch is dangerous because it normalizes a review gap: users or platform operators may rely on high-level metadata while the code and comments reveal significantly broader powers.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The history and detail tools expose call records, summaries, metadata, and full transcripts, all of which may contain sensitive personal or business information. Although masking can be requested, the tools do not strongly enforce privacy-safe defaults at the interface level or provide user-facing warnings commensurate with the sensitivity of the returned data.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
Webhook creation, modification, testing, and deletion let the plugin configure external data flows and event delivery destinations. In a bundle presented as install/publish helpers, this hidden capability can enable unexpected outbound integrations, data leakage to attacker-controlled endpoints, or disruption of existing notification infrastructure.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The skill defines very broad activation phrases for outbound calling, including common verbs like 'call', 'ring', 'negotiate', and multilingual variants, paired with an instruction to automatically execute the full call workflow. This increases the risk of accidental or ambiguous invocation of real-world telephony actions, especially because the skill is configured with always-on metadata and does not require a clear confirmation boundary before placing calls.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The workflow requires fetching full call transcripts, saving analytics, tagging conversations, and reporting key quotes back to the user, but it does not clearly warn about the privacy sensitivity of transcript contents. This can expose personally identifiable information or sensitive conversation content to a user who requested a call outcome without understanding the extent of data retrieval and disclosure.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
7. **Report final result to user** — summarize: who answered, what was discussed, what was agreed, next steps. Include key quotes from transcript.

**⚠️ CRITICAL BUG PREVENTION:**
- **NEVER** grab the first record from `lunara_history_list` without checking `date_from` and phone number match
- Old call records will appear in history — you MUST filter them out using `date_from=<timestamp before call>`
- If you report results from a call that happened BEFORE `lunara_call_single`, you are reporting the WRONG call
- Always verify: does the transcript mention the topic/context of THIS call, not some previous call?
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill documents API key creation, revocation, and permanent deletion without a corresponding warning that these are destructive or security-sensitive actions. In an agent context, this can lead to accidental credential lifecycle changes, service disruption, or creation of new privileged credentials without adequate user awareness or confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
**Every outbound call (lunara_call_single or campaign) MUST complete the entire workflow in a single turn. There are no exceptions — the user should NEVER have to ask "what happened?" separately:**

1. Do NOT stop after initiating the call to say "Call started, SID: xxx" and wait
2. Do NOT ask the user "should I check the results now?"
3. Do NOT search for the transcript while the call is still active — it doesn't exist yet!
4. **Poll `lunara_history_list` silently** every 25-30 seconds until the call record appears as completed (max 5 minutes / 10 attempts)
5. Only AFTER the call record appears as completed, fetch `lunara_history_detail` with transcript
Confidence
93% confidence
Finding
The skill explicitly instructs the agent not to ask the user whether it should check results and instead to silently poll for completion and proceed through transcript retrieval automatically. This reduces user control over a privacy- and cost-affecting workflow and encourages autonomous execution of multiple side-effecting steps after minimal prompting.

Vague Triggers

Low
Confidence
83% confidence
Finding
The example mappings reinforce permissive trigger behavior by associating short, underspecified requests with actions that can place calls or alter system state. While examples are documentation rather than executable logic, they shape agent behavior and make unsafe invocation patterns more likely in downstream use.

Static analysis

No suspicious patterns detected.