Back to skill

Security audit

Read AI

Security checks for vulnerabilities and agentic risk

Overview

This skill performs meeting-data work, but it needs Review because it stores highly sensitive meeting and lifelog records with weak controls.

Install only if you are comfortable storing meeting transcripts, participant details, webhook payloads, and Limitless lifelogs under ~/.readai. Do not expose the webhook endpoint publicly without adding signature or token validation, HTTPS, rate limits, and request-size limits. Use a restrictive umask or private directory/file permissions, and avoid --ai unless sending conversation excerpts through the Claude CLI is acceptable for your data.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/webhook_receiver.py:157
Finding

Unauthenticated webhook permits forged meeting-data injection

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/readai_client.py:136
Finding

Sensitive meeting and lifelog data is stored without enforced restrictive permissions

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • 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
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (29)

Tainted flow: 'params' from os.environ.get (line 49, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/limitless_pull.py (reported line 59)May include surrounding context.

python
if cursor:
            params["cursor"] = cursor

        resp = requests.get(f"{API_BASE}/lifelogs", headers=headers, params=params, timeout=30)
        resp.raise_for_status()
        data = resp.json()

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The skill advertises broad Read AI fetching, search, digest, analytics, and webhook functionality, but the documented implementation appears narrower and stores webhook payloads and source IPs locally. This discrepancy is dangerous because users may not realize the skill persists inbound meeting data and network metadata, creating privacy and compliance exposure.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

The skill advertises broad Read AI fetching, search, digest, analytics, and webhook functionality, but the documented implementation appears narrower and stores webhook payloads and source IPs locally. This discrepancy is dangerous because users may not realize the skill persists inbound meeting data and network metadata, creating privacy and compliance exposure.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The skill advertises broad Read AI fetching, search, digest, analytics, and webhook functionality, but the documented implementation appears narrower and stores webhook payloads and source IPs locally. This discrepancy is dangerous because users may not realize the skill persists inbound meeting data and network metadata, creating privacy and compliance exposure.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

chmod 600 ~/.config/readai/api-key

text

For Limitless: Get key from limitless.ai dashboard.
For Read AI API: Get key from Read AI Dashboard > Settings > Integrations > API Keys.

## Quick Commands

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

chmod 600 ~/.config/readai/api-key

text

For Limitless: Get key from limitless.ai dashboard.
For Read AI API: Get key from Read AI Dashboard > Settings > Integrations > API Keys.

## Quick Commands

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill manifest describes Read AI meeting functionality, but this script actually accesses Limitless pendant lifelog data using separate credentials and stores that data locally. This mismatch expands the skill's effective data access beyond user expectations and can cause unauthorized collection or handling of a different, highly sensitive data source.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill declares broad capabilities to read/write local files, access environment-stored credentials, invoke shell commands, and communicate over the network, but it does not constrain or disclose these powers with an explicit tool scope. That makes it easier for the skill to overreach its stated purpose and harder for users or reviewers to understand what data and system access it may exercise.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This skill handles highly sensitive meeting artifacts, including transcripts, summaries, action items, and webhook-delivered conversation data, yet it provides no privacy or data-handling warning. In this context, omission is risky because users may expose confidential business, legal, HR, or personal discussions without understanding storage, retention, or sharing implications.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

The skill instructs users to persist an API key in a fixed path under the home directory, creating durable credential storage outside a managed secret store. While common, this increases risk from local compromise, accidental backup/sync leakage, and ambiguous lifecycle management of a key that can unlock sensitive meeting and lifelog data.

Content

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

bash
# One-time setup
mkdir -p ~/.config/readai
echo "YOUR_API_KEY" > ~/.config/readai/api-key
chmod 600 ~/.config/readai/api-key

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 27)May include surrounding context.

One-time setup

mkdir -p ~/.config/readai echo "YOUR_API_KEY" > ~/.config/readai/api-key chmod 600 ~/.config/readai/api-key

text

For Limitless: Get key from limitless.ai dashboard.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This markdown file documents webhook payloads containing personal and potentially sensitive meeting content, including participant email addresses and full transcript text. Under the markdown-file criteria for SQP-2, the description should warn users about privacy and data-handling implications, but no such warning appears in the webhook section or setup steps.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · scripts/limitless_pull.py (reported line 24)May include surrounding context.

python
TIMEZONE = os.environ.get("READAI_TIMEZONE", "America/Chicago")
TZ = ZoneInfo(TIMEZONE)
API_BASE = "https://api.limitless.ai/v1"
API_KEY_PATH = os.path.expanduser("~/.config/readai/api-key")
DATA_DIR = Path(os.path.expanduser("~/.readai/lifelogs"))
PAGE_LIMIT = 10

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The code invokes an external Claude CLI to process meeting/lifelog content even though that capability is not justified by the declared skill scope. Hidden secondary processing of sensitive transcripts increases privacy and compliance risk because users may believe data is only being fetched from the advertised source, not forwarded elsewhere for inference.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The AI summary feature transmits highly sensitive conversation content, participant names, and action items to an external AI tool without any explicit privacy notice or consent flow. In a meeting-data skill, this is especially dangerous because transcripts may contain confidential business, personal, legal, or regulated information, and users are not warned that it will be shared for summarization.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
84% confidence
Finding

The script sends meeting/lifelog content to an external local CLI tool (claude) for summarization. While the subprocess invocation itself is not shell-injection prone because it uses an argument list, it still causes sensitive transcript data to leave the script's primary trust boundary and be processed by another tool/service, which is risky for private meeting data.

Content

Scanner excerpt · scripts/limitless_pull.py (reported line 173)May include surrounding context.

python
Keep it concise and actionable."""

    try:
        result = subprocess.run(
            ["claude", "-p", "--model", "sonnet"],
            input=prompt, capture_output=True, text=True, timeout=120,
        )

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · references/api-reference.md (reported line 5)May include surrounding context.

md
# ---------------------------------------------------------------------------
TIMEZONE = "America/Chicago"
TZ = ZoneInfo(TIMEZONE)
API_BASE = "https://api.read.ai/v1"
API_KEY_PATH = os.path.expanduser("~/.config/readai/api-key")
DATA_DIR = Path(os.path.expanduser("~/.readai/meetings"))
INDEX_PATH = DATA_DIR / "index.json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · scripts/readai_client.py (reported line 28)May include surrounding context.

python
# ---------------------------------------------------------------------------
TIMEZONE = "America/Chicago"
TZ = ZoneInfo(TIMEZONE)
API_BASE = "https://api.read.ai/v1"
API_KEY_PATH = os.path.expanduser("~/.config/readai/api-key")
DATA_DIR = Path(os.path.expanduser("~/.readai/meetings"))
INDEX_PATH = DATA_DIR / "index.json"

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 · scripts/readai_client.py (reported line 42)May include surrounding context.

python
p = Path(API_KEY_PATH)
    if not p.exists():
        print(f"Error: API key not found at {API_KEY_PATH}", file=sys.stderr)
        print("Set up: mkdir -p ~/.config/readai && echo 'YOUR_KEY' > ~/.config/readai/api-key", file=sys.stderr)
        sys.exit(1)
    return p.read_text().strip()

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The client persistently caches meeting metadata and full meeting contents, potentially including transcripts, participant identities, summaries, and action items, to local disk without clear disclosure in the described skill behavior. This creates a confidentiality risk because highly sensitive meeting records may remain on disk longer than users expect and could be accessed by other local users, backup systems, or later processes.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The code establishes local storage locations for cached meeting data and transcripts without any user-facing warning that sensitive conversational data will be written to disk. For meeting software, this is particularly risky because transcripts and participant details commonly include confidential business or personal information.

Content

No source excerpt is available for this finding.

Tainted flow: 'meeting_id' from requests.get (line 130, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/readai_client.py (reported line 90)May include surrounding context.

python
if include_transcript:
        params["include"] = "transcript"

    resp = requests.get(f"{API_BASE}/meetings/{meeting_id}", headers=headers, params=params, timeout=30)
    resp.raise_for_status()
    meeting = resp.json()

Tainted flow: 'meeting_id' from requests.get (line 130, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/readai_client.py (reported line 102)May include surrounding context.

python
def get_meeting_transcript(api_key: str, meeting_id: str) -> list[dict]:
    """Get the full transcript for a meeting."""
    headers = api_headers(api_key)
    resp = requests.get(f"{API_BASE}/meetings/{meeting_id}/transcript", headers=headers, timeout=30)
    resp.raise_for_status()
    data = resp.json()
    return data.get("transcript", data.get("entries", data.get("data", [])))

Tainted flow: 'meeting_id' from requests.get (line 130, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/readai_client.py (reported line 111)May include surrounding context.

python
def get_meeting_action_items(api_key: str, meeting_id: str) -> list[dict]:
    """Get action items for a meeting."""
    headers = api_headers(api_key)
    resp = requests.get(f"{API_BASE}/meetings/{meeting_id}/action-items", headers=headers, timeout=30)
    resp.raise_for_status()
    data = resp.json()
    return data.get("action_items", data.get("actionItems", data.get("data", [])))

Tainted flow: 'meeting_id' from requests.get (line 130, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/readai_client.py (reported line 120)May include surrounding context.

python
def get_meeting_summary(api_key: str, meeting_id: str) -> dict:
    """Get AI summary for a meeting."""
    headers = api_headers(api_key)
    resp = requests.get(f"{API_BASE}/meetings/{meeting_id}/summary", headers=headers, timeout=30)
    resp.raise_for_status()
    return resp.json()