Back to skill

Security audit

Attio Apikey

Security checks for vulnerabilities and agentic risk

Overview

This is a real Attio CRM client, but it gives an agent broad read/write/delete access with weak scoping and no delete safeguard.

Install only if you are comfortable giving this skill a dedicated, least-privilege Attio API key. Treat it as capable of broad Attio reads and writes, verify record IDs before any update or delete, avoid autonomous use of deletion, and avoid --all on large objects until pagination is fixed.

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

Warning
Location
attio_client.py:214
Finding
Broken Pagination Causes Unbounded API Requests and Memory Consumption<![CDATA[ ## Vulnerability Details **File Location**: `attio_client.py:214-238` **Vulnerability Type**: Unbounded loop caused by ineffective pagination **Risk Level**: Medium ### Vulnerable Code ```python if args.all: # Auto-paginate to get all records all_records = [] batch_size = 1000 offset = 0 if endpoint in ["deals", "deal"]: fetch_func = list_deals elif endpoint in ["companies", "company"]: fetch_func = list_companies elif endpoint in ["people", "person", "contacts"]: fetch_func = list_people else: fetch_func = lambda lim: query_records(endpoint, limit=lim) while True: result = fetch_func(limit=batch_size) if "error" in result: print(json.dumps(result, indent=2)) sys.exit(1) records = result.get("data", []) all_records.extend(records) if len(records) < batch_size: break offset += batch_size print(f"Fetched {len(all_records)}...", file=sys.stderr) ``` ### Technical Analysis The `--all` implementation increments the local `offset` variable after each full page, but it never passes that offset to `fetch_func`. The selected helper functions only receive `limit=batch_size`, so every iteration requests the same first page. When the first response contains exactly 1,000 records, the termination condition remains false. The same records are consequently downloaded and appended to `all_records` indefinitely. The process continues issuing authenticated requests and consuming memory until it encounters an API error, is terminated, or exhausts available resources. This behavior also affects custom object endpoints because the fallback lambda accepts only a limit and calls `query_records` without an offset. ### Attack Path 1. An operator or automation process configures a valid `ATTIO_API_KEY`. 2. The relevant Attio object contains at least 1,000 records, causing the first ...[truncated 1490 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Pass the current offset into every pagination request and impose explicit safety limits. Refactor the fetch functions to use a consistent signature, for example: ```python def list_companies(limit: int = 100, offset: int = 0): return query_records("companies", limit=limit, offset=offset) # Apply the same signature to list_deals and list_people. while True: result = fetch_func(limit=batch_size, offset=offset) if "error" in result: print(json.dumps(result, indent=2)) sys.exit(1) records = result.get("data", []) all_records.extend(records) if len(records) < batch_size: break offset += len(records) ``` For custom objects, preserve both arguments: ```python fetch_func = lambda limit, offset: query_records( endpoint, limit=limit, offset=offset, ) ``` Additional hardening should include: 1. Prefer pagination metadata or server-provided cursors over inferring completion solely from page length. 2. Add configurable maximum page and record counts to prevent accidental resource exhaustion. 3. Detect repeated page identifiers and abort if the server returns the same page. 4. Stream or incrementally process results instead of retaining every record in memory. 5. Return a nonzero exit status when pagination fails. 6. Add tests covering datasets with exactly 1,000 records, more than 1,000 records, repeated pages, empty pages, and API errors. ]]>
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
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (18)

Tainted flow: 'request' from os.environ.get (line 56, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
request.data = json.dumps(data).encode("utf-8")
    
    try:
        with urlopen(request, timeout=timeout) as response:
            body = response.read().decode("utf-8")
            if body:
                return json.loads(body)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
2. **Copy the env template:**
   ```bash
   cp .env.example .env
   ```

3. **Add your API key:**
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
3. **Add your API key:**
   ```bash
   # Edit .env and replace with your key
   ATTIO_API_KEY="your-key-here"
   ```
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
3. **Add your API key:**
   ```bash
   # Edit .env and replace with your key
   ATTIO_API_KEY="your-key-here"
   ```
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
3. **Add your API key:**
   ```bash
   # Edit .env and replace with your key
   ATTIO_API_KEY="your-key-here"
   ```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared behavior limits the skill to companies, people, and notes, but the implementation reportedly supports broader Attio resources and arbitrary raw endpoint access. This mismatch is dangerous because users may authorize the skill believing it has narrow CRM functionality while it can access or modify additional data types and endpoints.

Credential Access

High
Category
Privilege Escalation
Content
## Setup

1. Get API key from https://app.attio.com/settings/api
2. Copy `.env.example` to `.env` and add your key

## Quick Commands
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
API_KEY = os.environ.get("ATTIO_API_KEY", "")
if not API_KEY:
    # Fallback: read from .env file
    env_path = os.path.join(os.path.dirname(__file__), ".env")
    if os.path.exists(env_path):
        with open(env_path) as f:
            for line in f:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This markdown file describes a `--delete` option for removing records, but it does not include any user warning about deletion risk, reversibility, or need for confirmation. For markdown files, omission of warnings about behaviors that could affect user data or system integrity should be flagged.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill declares access to environment credentials and uses networked API access, but it does not specify any tool/permission scope. In an agent setting, missing explicit scope increases the chance the skill can be invoked with broader capabilities than users expect, especially because it uses a direct API key and remote CRUD operations.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The setup instructions tell users to place an API key directly into a local environment file without emphasizing its sensitivity or handling requirements. This can lead to insecure storage, accidental commits, or overbroad distribution of a credential that enables direct CRM access.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill advertises delete operations without any warning, confirmation guidance, or mention of irreversible consequences. In an agent-driven workflow, this raises the risk of accidental or prompt-induced destructive actions against production CRM records.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The module documentation emphasizes direct querying and fresh reads, but the code also performs create, update, delete, and note creation actions. In security-sensitive agent workflows, inaccurate documentation can mislead operators into granting or invoking mutating behavior they did not expect.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The skill description claims CRUD for companies, people, and notes, but the implementation also exposes broader Attio access such as deals, tasks, pipelines, object enumeration, and generic object queries. This mismatch increases the risk that users or calling agents invoke capabilities they did not knowingly authorize, expanding data exposure and modification scope.

External Transmission

Medium
Category
Data Exfiltration
Content
if not API_KEY:
    print(json.dumps({"error": "No API key found. Set ATTIO_API_KEY env var or create .env file"}))
    sys.exit(1)
BASE_URL = "https://api.attio.com/v2"

def make_request(endpoint: str, method: str = "GET", data: dict = None, timeout: int = 60) -> dict:
    """Make request to Attio API with proper headers and timeout."""
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The delete operation executes immediately when given an object type and record ID, with no confirmation, dry-run option, or safeguard. In an agent-operated environment, this raises the likelihood of accidental or prompt-induced destructive actions against production CRM data.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The CLI supports arbitrary endpoint access via --raw and also falls back to generic object querying for unrecognized endpoints, effectively turning the skill into a broad Attio API proxy. In an agent context, this can bypass the narrower business purpose described in metadata and enable unexpected reads or writes across the tenant.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The script reads an API credential from the ATTIO_API_KEY environment variable and falls back to a local .env file, but there is no user-facing notice that sensitive credentials are being accessed. For code files, access to sensitive environment variables or credentials should have some visible disclosure unless already clearly warned elsewhere.