Back to skill

Security audit

voice-agent-memory

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent voice-agent memory purpose, but its bridge exposes caller memory and paid AI access with weak or missing authentication controls.

Install only after reviewing the bridge code and hardening it: enforce token rejection, protect all history/admin endpoints, verify caller identity from the telephony provider, disable or gate automatic memory storage unless callers have consented, and avoid exposing the bridge publicly until those controls are fixed.

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
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
Findings (8)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
bridge/server.py:81
Finding
Authentication Is Deliberately Fail-Open<![CDATA[ ## Vulnerability Details **File Location**: `bridge/server.py:81-95`, `bridge/server.py:146-155` **Vulnerability Type**: Broken authentication / fail-open access control **Risk Level**: Critical ### Vulnerable Code ```python async def verify_token(request: Request) -> str: auth = request.headers.get("authorization", "") token = auth.replace("Bearer ", "").strip() # Also check x-api-key header (ElevenLabs sometimes uses this) x_api_key = request.headers.get("x-api-key", "") if not token and x_api_key: token = x_api_key.strip() if token and token != LLM_BRIDGE_TOKEN: logger.warning(f"Invalid auth token (accepting anyway): {token[:15]}...") return token or "anonymous" ``` The result is called but never checked: ```python raw_body = await request.body() body_str = raw_body.decode("utf-8", errors="replace") await verify_token(request) # Parse body body = {} ``` ### Technical Analysis `verify_token()` accepts all three authentication states: 1. A valid bridge token. 2. An invalid bridge token. 3. No token at all, represented as `"anonymous"`. The explicit `"accepting anyway"` branch makes the documented Bearer-token control nonfunctional. The caller does not inspect the returned identity or reject anonymous access. Because the bridge invokes Anthropic and BlueColumn with credentials held by the server, remote clients effectively gain indirect use of those privileged credentials even though the credentials themselves are not returned. The warning also logs the first 15 characters of an attacker-supplied token. Token values should not be logged because users may accidentally submit real credentials for another service. ### Attack Path 1. The operator exposes port 8013 through the documented Cloudflare or ngrok tunnel. 2. An attacker sends `POST /v1/chat/completions` with no `Authorization` header, or with any arbitrary Bearer token. 3. `verify_token()` returns `"anonymous"` or logs the mi ...[truncated 911 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Reject missing credentials with HTTP 401. - Reject invalid credentials with HTTP 401 or 403. - Compare secrets with `secrets.compare_digest()` to reduce timing leakage. - Authenticate every non-public endpoint, not only chat completion requests. - Never log any portion of a submitted credential. - Configure a strong, randomly generated bridge token and refuse startup when the default placeholder is still configured. - Add request rate limits, body-size limits, and API usage quotas. - Where supported, verify ElevenLabs, Deepgram, or telephony-provider signatures rather than relying only on a shared static token. Example: ```python import secrets async def verify_token(request: Request) -> None: auth = request.headers.get("authorization", "") token = auth.removeprefix("Bearer ").strip() if not token: token = request.headers.get("x-api-key", "").strip() if not LLM_BRIDGE_TOKEN or not secrets.compare_digest(token, LLM_BRIDGE_TOKEN): raise HTTPException(status_code=401, detail="Invalid authentication") ``` ]]>

T01 · Skill Instruction Hijacking

Error
Location
bridge/server.py:179
Finding
Untrusted Request Content Is Injected into the System Prompt<![CDATA[ ## Vulnerability Details **File Location**: `bridge/server.py:179-195`, `bridge/server.py:339-344`, `bridge/prompt_builder.py:68-70` **Vulnerability Type**: System-prompt injection **Risk Level**: High ### Vulnerable Code The bridge treats system messages supplied by the remote client as additional system context: ```python # Convert to Anthropic message format (skip system messages) claude_messages = [] system_content = "" for m in messages: role = m.get("role", "user") content = m.get("content", "") if role == "system": system_content += content + "\n" else: claude_messages.append({"role": role, "content": content}) ``` That content is passed to the prompt builder: ```python system_prompt = build_system_prompt( caller_number=caller_number, bluecolumn_recall=recall_context if recall_context else None, additional_context=system_content if system_content else None ) ``` The prompt builder concatenates it directly into the trusted prompt: ```python if additional_context: caller_section += f"\n### Additional Context\n{additional_context}\n" ``` ### Technical Analysis The request body is controlled by the remote client. A client can assign arbitrary content the `system` role, after which the bridge places that content inside the system prompt sent to Anthropic. This erases the intended trust boundary between application-controlled instructions and caller-controlled data. Markdown headings such as “Additional Context” do not provide a security boundary and do not prevent the model from interpreting embedded text as instructions. This is particularly serious because authentication is also fail-open. However, even after authentication is corrected, provider-supplied or caller-influenced metadata must still not be granted system-level authority. ### Attack Path 1. An attacker submits a chat-completion request containing a message with `"role": "system"`. 2. The message instructs the model to ignore t ...[truncated 982 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not accept client-supplied `system` messages. - Permit only `user` and `assistant` roles from the external OpenAI-compatible interface. - Construct the system prompt exclusively from server-controlled templates and validated structured fields. - Reject unknown roles and non-string message content. - Treat provider metadata as untrusted data, even when the provider is authenticated. - If contextual text must be included, clearly label and encode it as quoted data and explicitly instruct the model never to follow instructions contained within it. - Apply strict length and character limits to contextual fields. - Add adversarial tests for nested headings, “ignore previous instructions,” fake rule blocks, and prompt-boundary injection. ]]>

T02 · Agent Memory Poisoning

Error
Location
bridge/server.py:196
Finding
Automatic Transcript Storage Enables Persistent Memory Poisoning<![CDATA[ ## Vulnerability Details **File Location**: `bridge/server.py:196-210`, `bridge/server.py:274-279`, `bridge/memory.py:109-158`, `bridge/prompt_builder.py:49-57` **Vulnerability Type**: Persistent attacker-controlled memory injection **Risk Level**: High ### Vulnerable Code Recent client messages are converted into a transcript: ```python # Build conversation text for post-call storage conv_text = "\n".join([ f"{m.get('role','user')}: {str(m.get('content',''))[:300]}" for m in claude_messages[-6:] ])[:3000] ``` Storage is enabled asynchronously by default: ```python # Fire-and-forget memory storage after call if AUTO_STORE_MEMORY: asyncio.ensure_future( store_conversation(conv_text, caller_number, f"Voice call - {caller_number} - {call_sid}") ) ``` The transcript is sent to persistent BlueColumn memory: ```python resp = await client.post( f"{BLUECOLUMN_BASE}/agent-remember", headers={ "Authorization": f"Bearer {BLUECOLUMN_API_KEY}", "Content-Type": "application/json" }, json={ "text": transcript[:5000], # Cap at 5000 chars "title": effective_title[:200] } ) ``` Recalled data is later inserted into the system prompt: ```python if bluecolumn_recall: memory_section = f""" ## 🧠 Cross-Call Memory (from BlueColumn) {bluecolumn_recall} This memory was automatically recalled from past conversations with this caller. Use it to personalize the conversation and avoid asking for information they've already shared. """ ``` ### Technical Analysis Attacker-controlled conversation content is automatically written to long-term memory without moderation, provenance labeling, confirmation, or caller verification. On a subsequent request, semantically recalled memory is placed directly in the model's system prompt. Consequently, an instruction planted during one request can influence later requests. The poisoning can also be associated with a spoofed victim phone number because c ...[truncated 1423 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store memory only after authenticated call completion and verified caller identity. - Require explicit operator or caller consent before enabling automatic transcript persistence. - Separate raw transcripts from trusted profile facts. - Run candidate memories through moderation, instruction detection, and structured extraction. - Store provenance, timestamp, call identifier, authenticated identity, and trust level with every memory. - Never inject raw recalled text into the system-instruction layer. - Retrieve memory through exact account and caller namespace filters rather than semantic search alone. - Require confirmation before promoting caller statements into durable preferences or rules. - Support deletion, retention limits, and correction of poisoned records. - Cancel or await storage tasks cleanly rather than scheduling untracked fire-and-forget work. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
bridge/server.py:216
Finding
Caller Identity Spoofing Defeats Memory Isolation and Contact Screening<![CDATA[ ## Vulnerability Details **File Location**: `bridge/server.py:216-239`, `bridge/memory.py:28-47`, `bridge/memory.py:69-91`, `contacts.json:1-7` **Vulnerability Type**: Caller impersonation and authorization bypass **Risk Level**: High ### Vulnerable Code Caller identity is taken from arbitrary request fields: ```python def extract_caller_number(body: dict, messages: list) -> str: """Extract caller phone number from ElevenLabs request metadata.""" # Check top-level metadata metadata = body.get("metadata", {}) or {} caller = metadata.get("caller_number", "") or metadata.get("from_number", "") or metadata.get("phone", "") # Check system messages for caller info if not caller: for m in messages: if m.get("role") == "system": content = m.get("content", "") # ElevenLabs injects caller info in system message like: # "Caller number: +12065550123" import re match = re.search(r'(\+?\d[\d\-\(\)\s]{7,}\d)', content) if match: caller = match.group(1).strip() break # Clean up caller = caller.replace("-", "").replace("(", "").replace(")", "").replace(" ", "") return caller ``` That unverified identifier is used to form a memory query: ```python def _build_recall_query(caller_number: str) -> str: name = _lookup_caller_name(caller_number) if name: return f"past conversations with {name} ({caller_number}) history context preferences" return f"caller {caller_number} past conversations history context preferences" ``` The contacts file claims unlisted callers will be screened: ```json { "+12065550123": { "name": "Example Contact", "role": "Example user", "notes": "Replace with your real contacts. Callers not listed will be screened." } } ``` No screening or allowlist enforcement is implemented. ### Technical Analysis A caller id ...[truncated 1672 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Accept caller identity only from verified, provider-controlled metadata. - Validate Twilio, ElevenLabs, or Deepgram webhook signatures before processing requests. - Do not infer authorization identity from conversational text or system-message content. - Normalize and validate numbers using a proper E.164 phone-number library. - Enforce the contact allowlist before recall or storage if screening is a declared requirement. - Reject unknown callers or route them through a restricted first-contact flow. - Use server-enforced structured namespaces such as account ID plus verified caller ID. - Ensure BlueColumn queries contain mandatory metadata filters; do not rely on semantic text to isolate callers. - Add tests proving that a request cannot claim a different phone number from the provider-verified identity. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
bridge/server.py:428
Finding
Call History Is Exposed Without Authentication<![CDATA[ ## Vulnerability Details **File Location**: `bridge/server.py:428-445` **Vulnerability Type**: Unauthenticated sensitive metadata disclosure **Risk Level**: High ### Vulnerable Code ```python @app.get("/calls/history") async def call_history(): """View recent call history.""" if not os.path.exists(COST_LOG): return {"calls": []} calls = [] with open(COST_LOG) as f: for line in f: line = line.strip() if line: try: calls.append(json.loads(line)) except: pass # Return last 20 calls return {"calls": calls[-20:]} ``` The stored entries include caller numbers and operational metadata: ```python entry = { "call_sid": call_sid, "timestamp": datetime.now(timezone.utc).isoformat(), "caller": caller, "duration_sec": round(duration, 2), "total_cost_usd": round(total_cost, 4), "breakdown": breakdown } ``` ### Technical Analysis `/calls/history` does not invoke `verify_token()` and has no authorization or administrative role check. It reads the local JSONL cost log and returns the last 20 records verbatim. When the service is exposed through the documented public tunnel, this route may be reachable by anyone who knows or discovers its path. ### Attack Path 1. The bridge is exposed through Cloudflare Tunnel or ngrok. 2. An attacker requests `GET /calls/history` without credentials. 3. The endpoint reads `costs.jsonl`. 4. The endpoint returns recent call records containing caller and usage information. 5. The attacker repeats the request over time to monitor call activity. ### Impact Assessment An unauthenticated party can obtain: - Caller phone numbers. - Call identifiers. - Call timestamps. - Call durations. - Estimated costs and service breakdowns. This reveals personal and operational metadata and can facilitate caller enumeration or activity monitoring. The endpoint does not expose t ...[truncated 29 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require authentication and an administrative authorization role. - Keep administrative endpoints on a separate management interface that is not publicly tunneled. - Redact or hash phone numbers before returning records. - Return only fields required for the administrative task. - Add pagination and audit logging for history access. - Apply restrictive cache headers. - Consider disabling the endpoint by default in production. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/test-call.sh:7
Finding
Shell Scripts Unsafely Evaluate Dotenv Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/test-call.sh:7-10`, `scripts/test-recall.sh:13-17` **Vulnerability Type**: Shell command injection through configuration parsing **Risk Level**: Medium ### Vulnerable Code In `scripts/test-call.sh`: ```bash # Load .env if [ -f "$SCRIPT_DIR/.env" ]; then export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs) fi ``` In `scripts/test-recall.sh`: ```bash # Load API key from .env SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" if [ -f "$SCRIPT_DIR/.env" ]; then export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs) fi ``` ### Technical Analysis The scripts transform `.env` file contents into shell words through command substitution and unquoted expansion. This is not the pre-scan's suspected `curl | bash` pattern; the scripts do not execute downloaded response bodies as shell code. Nevertheless, the dotenv-loading construct is unsafe. Shell syntax contained in the generated words may be interpreted unexpectedly, and whitespace, quotes, comments, wildcard characters, and multiline values are not parsed according to dotenv semantics. A crafted or compromised `.env` can therefore affect command execution or alter exported variables. The `.env` also contains high-value API credentials, increasing the consequence of unsafe parsing. ### Attack Path 1. An attacker or compromised installation process modifies the Skill's `.env`. 2. The attacker inserts a value containing shell metacharacters or command-substitution syntax. 3. A user runs `scripts/test-call.sh` or `scripts/test-recall.sh`. 4. The script expands the output of `grep | xargs` in the shell. 5. Crafted content may be interpreted as shell syntax or alter the script's environment and subsequent network requests. ### Impact Assessment Exploitation occurs with the privileges of the user running the test script. Possible consequences include: - Execution of local commands. - Theft or modification of API credentials. - Redirection of bridge reques ...[truncated 211 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not parse dotenv files with `export $(...)`, `xargs`, `eval`, or unquoted shell expansion. - Prefer invoking a small Python program that loads the file using `python-dotenv`. - Alternatively, parse only an explicit allowlist of simple keys and reject unsafe characters. - Set `.env` permissions to owner-read/write only, such as mode `0600`. - Validate `BRIDGE_URL` and other destination variables before use. - Document that `.env` must never be obtained from an untrusted source. A safer pattern is to perform the API request from Python after calling: ```python from dotenv import load_dotenv load_dotenv("/trusted/path/.env") ``` ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/test-call.sh:12
Finding
Test Call Script Constructs JSON Through Unsafe String Interpolation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/test-call.sh:12-15`, `scripts/test-call.sh:22-33` **Vulnerability Type**: JSON injection and malformed request construction **Risk Level**: Low ### Vulnerable Code ```bash MESSAGE="${1:-Hello, this is Joe calling about BlueColumn.}" BRIDGE_URL="${BRIDGE_URL:-http://localhost:8013}" BRIDGE_TOKEN="${LLM_BRIDGE_TOKEN:-bluecolumn-voice-bridge-YOUR_TOKEN}" CALLER_NUMBER="${CALLER_NUMBER:-+12065550123}" ``` ```bash curl -s -X POST "$BRIDGE_URL/v1/chat/completions" \ -H "Authorization: Bearer $BRIDGE_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"model\": \"claude-sonnet-4\", \"messages\": [ {\"role\": \"system\", \"content\": \"Caller number: $CALLER_NUMBER\"}, {\"role\": \"user\", \"content\": \"$MESSAGE\"} ], \"stream\": false, \"metadata\": {\"caller_number\": \"$CALLER_NUMBER\"} }" | python3 -c " ``` ### Technical Analysis `MESSAGE` and `CALLER_NUMBER` are inserted directly into a JSON string without JSON encoding. Quotes, backslashes, newlines, and control characters can terminate or alter the intended JSON values. This can result in malformed requests or allow a caller of the local test script to add or modify JSON fields. The issue does not directly create native shell command execution because these variables are expanded inside an already parsed double-quoted shell argument. ### Attack Path 1. An attacker persuades a user to test a specially crafted message or controls the `CALLER_NUMBER` environment variable. 2. The value contains JSON delimiters such as quotes, braces, or commas. 3. The script inserts the value into the request body without escaping. 4. The resulting payload changes the intended message structure or becomes invalid. 5. If the bridge accepts the altered structure, the attacker-controlled fields influence caller extraction or model processing. ### Impact Assessment The primary impacts are: - Malformed test requests. - Modi ...[truncated 256 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Construct JSON with a JSON-aware tool rather than shell interpolation. - Use `jq -n --arg` or Python's `json.dumps()`. - Validate caller numbers against a strict E.164 pattern. - Reject control characters and enforce reasonable message-length limits. - Pass the generated JSON to `curl` using `--data-binary`. Example: ```bash payload="$(jq -n \ --arg message "$MESSAGE" \ --arg caller "$CALLER_NUMBER" \ '{ model: "claude-sonnet-4", messages: [ {role: "user", content: $message} ], stream: false, metadata: {caller_number: $caller} }')" curl --data-binary "$payload" ... ``` ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Dependencies Are Installed Without Exact Version or Integrity Pinning<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-5` **Vulnerability Type**: Non-reproducible and insufficiently constrained dependency installation **Risk Level**: Medium ### Vulnerable Code ```text fastapi>=0.110.0 uvicorn>=0.27.0 httpx>=0.27.0 anthropic>=0.30.0 python-dotenv>=1.0.0 ``` The documentation instructs users to install these unconstrained dependencies: ```bash pip install -r requirements.txt ``` ### Technical Analysis Every dependency uses only a lower bound. A future installation can therefore select any later compatible release available from the configured package index. There is no lockfile or hash verification. This makes installations non-reproducible and increases exposure to future compromised releases, unexpected major-version behavior, or dependency-resolution changes. No typosquatted package or currently malicious package was identified in the audited file; the finding concerns the unsafe version and integrity policy. ### Attack Path 1. A user runs the documented `pip install -r requirements.txt`. 2. The resolver selects the newest releases satisfying the lower bounds at that time. 3. A future compromised, vulnerable, or incompatible release is downloaded. 4. Package installation or import executes code from that release. 5. The dependency code runs with the privileges of the user operating the bridge. ### Impact Assessment A compromised dependency could access: - Anthropic and BlueColumn API keys loaded into the process. - Caller phone numbers and conversation text. - Local transcript and cost-log directories. - Network access available to the bridge process. - Files available to the operating-system account. The current repository does not itself prove that any listed package version is malicious. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin every direct and transitive dependency to reviewed versions. - Generate and commit a lockfile using `pip-tools`, Poetry, or another controlled resolver. - Require package hashes during installation with `pip --require-hashes`. - Use a trusted package index and prevent dependency-index fallback where possible. - Scan locked packages for known vulnerabilities in CI. - Review upgrades through a controlled pull-request process. - Rebuild the lockfile periodically rather than resolving unrestricted versions on production systems. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (48)

Credential Access

High
Category
Privilege Escalation
Content
- "https://api.anthropic.com"
  files:
    read:
      - "~/.openclaw/workspace/skills/voice-agent-memory/.env"
      - "~/.openclaw/workspace/skills/voice-agent-memory/contacts.json"
      - "~/.openclaw/workspace/memory/voice-calls/"
    write:
Confidence
95% confidence
Finding
The skill requests read access to its `.env` file, which commonly contains API keys, auth tokens, and other secrets for Twilio, Anthropic, ElevenLabs, and BlueColumn. Granting runtime access to raw credential stores materially increases the blast radius of any compromise or misuse, especially in a skill that also performs network operations to multiple external services.

Missing User Warnings

High
Confidence
96% confidence
Finding
The code sends call transcripts to an external BlueColumn service after the call, but there is no visible consent check, notice mechanism, redaction step, or policy gate before transmitting potentially sensitive conversation data. In a voice-agent context, transcripts can contain personal, financial, health, or authentication information, so exporting them to a third party creates a significant privacy and compliance risk.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The authentication helper extracts a bearer or x-api-key token, but if the token is invalid it only logs a warning and still returns control to the caller. Because /v1/chat/completions and related routes proceed regardless of auth result, any unauthenticated remote party can invoke the bridge, trigger LLM calls, and access caller-linked memory behavior, making this an authentication bypass rather than a logging issue.

Missing User Warnings

High
Confidence
97% confidence
Finding
The code writes call transcripts containing caller identifiers and message content to local storage in `log_transcript`, and elsewhere auto-stores conversation text to BlueColumn memory. While the module docstring mentions storing transcripts, there is no user-facing warning, confirmation, or privacy disclosure to callers about persistence of their conversation data.

Missing User Warnings

High
Confidence
94% confidence
Finding
The bridge sends message content and caller-linked memory context to Anthropic and BlueColumn, which is a network transmission of potentially sensitive user and system data. The file contains implementation comments and startup output, but no user-facing disclosure or confirmation that external AI and memory services receive call content and caller metadata.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The /calls/history endpoint returns recent call records from costs.jsonl with no authentication or authorization checks. Those records include caller identifiers, timestamps, durations, and cost metadata, which exposes sensitive operational and caller data to anyone who can reach the service.

Possible Typosquatting: 'uvicorn' resembles popular package 'gunicorn'

High
Category
Supply Chain
Confidence
70% confidence
Finding
Package name closely resembles a popular package, suggesting possible typosquatting. Attackers publish malicious packages with similar names to trick developers into installing them.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Check for .env
if [ ! -f "$SCRIPT_DIR/.env" ]; then
    echo "⚠️  No .env file found. Copy from .env.example:"
    echo "   cp $SCRIPT_DIR/.env.example $SCRIPT_DIR/.env"
    echo "   Then edit with your API keys."
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Check for .env
if [ ! -f "$SCRIPT_DIR/.env" ]; then
    echo "⚠️  No .env file found. Copy from .env.example:"
    echo "   cp $SCRIPT_DIR/.env.example $SCRIPT_DIR/.env"
    echo "   Then edit with your API keys."
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Check for .env
if [ ! -f "$SCRIPT_DIR/.env" ]; then
    echo "⚠️  No .env file found. Copy from .env.example:"
    echo "   cp $SCRIPT_DIR/.env.example $SCRIPT_DIR/.env"
    echo "   Then edit with your API keys."
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Check for .env
if [ ! -f "$SCRIPT_DIR/.env" ]; then
    echo "⚠️  No .env file found. Copy from .env.example:"
    echo "   cp $SCRIPT_DIR/.env.example $SCRIPT_DIR/.env"
    echo "   Then edit with your API keys."
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Check for .env
if [ ! -f "$SCRIPT_DIR/.env" ]; then
    echo "⚠️  No .env file found. Copy from .env.example:"
    echo "   cp $SCRIPT_DIR/.env.example $SCRIPT_DIR/.env"
    echo "   Then edit with your API keys."
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Load .env
if [ -f "$SCRIPT_DIR/.env" ]; then
    export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Load .env
if [ -f "$SCRIPT_DIR/.env" ]; then
    export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Load .env
if [ -f "$SCRIPT_DIR/.env" ]; then
    export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Load .env
if [ -f "$SCRIPT_DIR/.env" ]; then
    export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Load .env
if [ -f "$SCRIPT_DIR/.env" ]; then
    export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Load .env
if [ -f "$SCRIPT_DIR/.env" ]; then
    export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
fi
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

# Load .env
if [ -f "$SCRIPT_DIR/.env" ]; then
    export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
fi
Confidence
90% confidence
Finding
The script loads and exports values from .env using `export $(grep ... | xargs)`, which is an unsafe parsing pattern for secret-bearing files. Malformed entries, whitespace, shell-special characters, or attacker-controlled .env content can corrupt the environment or trigger unintended command behavior in later steps, and secrets are broadly exposed to subprocesses unnecessarily.

Credential Access

High
Category
Privilege Escalation
Content
# Load .env
if [ -f "$SCRIPT_DIR/.env" ]; then
    export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
fi

MESSAGE="${1:-Hello, this is Joe calling about BlueColumn.}"
Confidence
88% confidence
Finding
This line completes the unsafe bulk export of all .env values into the process environment, increasing secret exposure to child processes like `curl` and `python3`. If the .env file is modified by another party or contains unexpected syntax, the script may behave unpredictably and leak more configuration than intended.

External Script Fetching

High
Category
Supply Chain
Content
echo "🔗 Bridge: $BRIDGE_URL/v1/chat/completions"
echo ""

curl -s -X POST "$BRIDGE_URL/v1/chat/completions" \
  -H "Authorization: Bearer $BRIDGE_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
echo "🔍 Recalling BlueColumn memory for caller: $PHONE"
echo ""

curl -s -X POST "https://xkjkwqbfvkswwdmbtndo.supabase.co/functions/v1/agent-recall" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $BLUECOLUMN_API_KEY" \
  -d "{\"q\": \"What do I know about caller $PHONE?\"}" | python3 -c "
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The architecture explicitly describes automatic transcript storage and caller-ID-based memory recall tied to phone numbers, but it provides no indication of consent, notice, retention limits, or access controls. In a voice-agent context handling real callers, this creates a meaningful privacy and compliance risk because personal data and conversation history may be collected, linked, and reused without user awareness.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The call lifecycle states that post-call transcripts are sent to an external memory service, again without any explicit disclosure to users that their conversations are being stored and shared with a third-party system. This is dangerous because voice conversations often contain sensitive personal or business information, and undisclosed external storage increases privacy, legal, and data-governance exposure.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill prominently advertises that it records, transcribes, stores, and recalls caller-linked conversation history, but it does not provide a clear user-facing warning about the privacy implications of automatic transcript retention and caller-identity correlation. In a live telephony context, this omission is risky because users may unknowingly disclose sensitive personal or business information that is then persisted to external memory services and local logs.

Static analysis

No suspicious patterns detected.