Back to skill

Security audit

speko-calls

Security checks for vulnerabilities and agentic risk

Overview

This skill is a clearly disclosed phone-calling integration, but one example handles call IDs unsafely in a way that could expose the platform key.

Review before installing. Use this only where paid outbound calls are approved, keep the platform key least-privileged and rotated, require explicit confirmation for each destination, and fix or avoid the $ID examples by validating UUIDs and quoting the full URL before running status or report commands.

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 (1)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:85
Finding

Unquoted Call Identifier Allows curl Argument Injection and Potential Credential Disclosure

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 85–89
Vulnerability Type: Shell argument injection
Risk Level: High

Vulnerable Code

bash
curl -s https://api.speko.dev/v1/calls/$ID \
  -H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" | jq '{status, duration_seconds, ended_at}'

curl -s https://api.speko.dev/v1/calls/$ID/report \
  -H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" | jq '{summary, outcome, cost_micro_usd}'

Technical Analysis

The call identifier is interpolated into both curl commands without quoting or validation. In a POSIX-compatible shell, an unquoted variable is subject to field splitting and pathname expansion. Although the document states that call identifiers are UUIDs, the example commands do not enforce that requirement before using $ID.

If attacker-controlled input containing whitespace is assigned to $ID, curl can interpret the resulting fields as additional command-line options or request URLs rather than as one URL path component. The fixed Authorization header may then be applied to an attacker-controlled URL included in the injected arguments.

This is an argument-injection issue rather than direct shell metacharacter execution: shell operators embedded inside an expanded variable are not reparsed as shell syntax. Nevertheless, curl's support for multiple URLs and numerous command-line options provides a practical exploitation surface.

Attack Path

  1. An attacker supplies a crafted value while presenting it as a call identifier.
  2. The agent or operator assigns that value to $ID without checking that it is a single valid UUID.
  3. The value is expanded unquoted in one of the documented curl commands.
  4. Shell field splitting converts whitespace-separated content into multiple curl arguments.
  5. curl interprets an injected field as another URL or option.
  6. A request may be made to an attacker-controlled endpoint with the platform bearer ...[truncated 743 chars]
Remediation
View remediation

Remediation Suggestions

  1. Validate the identifier before invoking curl. Accept only a canonical UUID and reject every other value:

    bash
    case "$ID" in
      *[!0-9A-Fa-f-]*|'')
        printf '%s\n' "Invalid call ID" >&2
        exit 1
        ;;
    esac
    
    if ! printf '%s\n' "$ID" |
      grep -Eq '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[1-5][0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$'; then
      printf '%s\n' "Invalid call ID" >&2
      exit 1
    fi
    
  2. Quote the complete URL so the identifier cannot undergo field splitting or pathname expansion:

    bash
    curl --silent --show-error --fail-with-body \
      -H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" \
      -- "https://api.speko.dev/v1/calls/$ID" |
      jq '{status, duration_seconds, ended_at}'
    
    curl --silent --show-error --fail-with-body \
      -H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" \
      -- "https://api.speko.dev/v1/calls/$ID/report" |
      jq '{summary, outcome, cost_micro_usd}'
    
  3. Do not execute either request unless UUID validation succeeds. Avoid attempting to sanitize malformed input into a valid identifier.

  4. Configure curl defensively: reject unexpected redirects, use an explicit HTTPS-only protocol policy where supported, and avoid features such as configuration-file loading when processing untrusted input.

  5. If these commands were previously used with untrusted identifiers, rotate the platform API key and review platform audit logs for requests from unexpected sources, unauthorized calls, and transcript or report access.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (6)

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

md
## Credential

`SPEKO_PLATFORM_API_KEY` — a **platform** key, from the Speko dashboard. Host is
`https://api.speko.dev/v1`.

This is not the router key the `speko` skill uses. The two do not cross: a router key returns
401 here, and a platform key returns 401 on `api.speko.ai`. Keys are also environment-scoped,

External Transmission

Medium
Category
Data Exfiltration
Confidence
82% confidence
Finding

This command transmits the platform API key to an external service and enumerates the organization's phone numbers, which is sensitive operational metadata. In context this is intended functionality, but exposure or misuse could reveal owned numbers and facilitate unauthorized outbound calling setup.

Content

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

List the numbers you can call from

bash
curl -s https://api.speko.dev/v1/phone-numbers \
  -H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" | jq '.[] | {id, e164, direction}'

External Transmission

Medium
Category
Data Exfiltration
Confidence
89% confidence
Finding

The external transmission here is the actual call-initiation request, which sends destination numbers and call script content to a third-party API and can immediately trigger a billed phone call. Because the action is irreversible and affects real people, the skill context makes this significantly more dangerous than a generic HTTP request.

Content

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

Place the call

bash
curl -s https://api.speko.dev/v1/sessions/phone \
  -H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "to": "+15551234567",

External Transmission

Medium
Category
Data Exfiltration
Confidence
89% confidence
Finding

The external transmission here is the actual call-initiation request, which sends destination numbers and call script content to a third-party API and can immediately trigger a billed phone call. Because the action is irreversible and affects real people, the skill context makes this significantly more dangerous than a generic HTTP request.

Content

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

Place the call

bash
curl -s https://api.speko.dev/v1/sessions/phone \
  -H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "to": "+15551234567",

External Transmission

Medium
Category
Data Exfiltration
Confidence
71% confidence
Finding

Fetching call status and reports sends authenticated requests to an external service and may retrieve sensitive operational data about completed calls. The report surface includes summaries, outcomes, and potentially personal or regulated call content, so unauthorized access or overexposure would be harmful.

Content

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

Follow the call

bash
curl -s https://api.speko.dev/v1/calls/$ID \
  -H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" | jq '{status, duration_seconds, ended_at}'

curl -s https://api.speko.dev/v1/calls/$ID/report \

External Transmission

Medium
Category
Data Exfiltration
Confidence
78% confidence
Finding

The report endpoint can return call summaries, transcripts, structured data, and cost information, and the skill explicitly notes that structured_data may echo the dialed phone number. This creates a genuine data-exposure risk if outputs are shown broadly, stored insecurely, or passed into contexts that should redact phone numbers.

Content

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

curl -s https://api.speko.dev/v1/calls/$ID
-H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" | jq '{status, duration_seconds, ended_at}'

curl -s https://api.speko.dev/v1/calls/$ID/report
-H "Authorization: Bearer $SPEKO_PLATFORM_API_KEY" | jq '{summary, outcome, cost_micro_usd}'

text