Back to skill

Security audit

Swiss Phone Directory

Security checks for vulnerabilities and agentic risk

Overview

This is a straightforward search.ch Swiss phone-directory lookup skill with disclosed API key and network use, but users should be careful with personal contact data and persistent API-key storage.

Install only if you are comfortable sending lookup terms, locations, phone numbers, and your search.ch API key to search.ch. Avoid storing the API key in shell profile files on shared or heavily automated systems; use per-session environment variables, a gateway secret, or a secrets manager when possible. Treat returned names, addresses, emails, and websites as untrusted display data, especially if shown in Markdown-capable clients.

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
searchch.py:144
Finding
Unsanitized Remote Directory Data Allows Terminal and Markdown Output Injection<![CDATA[ ## Vulnerability Details **File Location**: `searchch.py:144-160`, `searchch.py:219-234` **Vulnerability Type**: Untrusted output injection through terminal and Markdown rendering **Risk Level**: Medium ### Vulnerable Code ```python # Extra fields (fax, email, website) for extra in entry.findall("tel:extra", NS): extra_type = extra.get("type", "") value = extra.text if value: value = value.rstrip("*") # Remove no-promo marker if extra_type == "fax": result["fax"] = format_phone(value) elif extra_type == "email": result["email"] = value elif extra_type == "website": # Parse "label: url" format if ": http" in value: result["website"] = value.split(": ", 1)[1] elif value.startswith("http"): result["website"] = value elif "website" not in result: result["website"] = value ``` ```python for i, r in enumerate(results, 1): # Name and type type_icon = "🏢" if r.get("type") == "Organisation" else "👤" print(f"{type_icon} **{r.get('name', 'Unbekannt')}**") # Occupation/subtitle if r.get("occupation"): print(f" {r['occupation']}") # Address addr_parts = [] if r.get("street"): addr_parts.append(r["street"]) if r.get("zip") or r.get("city"): addr_parts.append(f"{r.get('zip', '')} {r.get('city', '')}".strip()) if r.get("canton"): addr_parts[-1] = f"{addr_parts[-1]} {r['canton']}" if addr_parts else r["canton"] if addr_parts: print(f" 📍 {', '.join(addr_parts)}") # Contact - phone numbers with clickable tel: links if r.get("phone"): phone_display = format_phone(r['phone'], clickable=clickable) if clickable else r['phone'] print(f" 📞 {phone_display}") if r.get("fax"): fax_display = format_phone(r['fax'], clickable=clickable) if clickable else r['fax'] print(f" 📠 {f ...[truncated 3067 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Remove terminal control characters from every remotely sourced field.** Reject or strip ANSI escape sequences and nonprinting control characters before storing or displaying API values. 2. **Escape output for its rendering context.** When generating Markdown, escape characters such as `[`, `]`, `(`, `)`, backticks, asterisks, underscores, and backslashes in untrusted labels and text. 3. **Validate website URLs using a parsed scheme allowlist.** Use `urllib.parse.urlparse()` and accept only explicitly supported schemes, preferably `https` and optionally `http`. Do not treat arbitrary text as a website URL. 4. **Construct links from validated components.** Keep the escaped display label separate from the validated destination instead of accepting embedded Markdown from the API. 5. **Apply sanitization consistently.** Protect names, occupations, street addresses, cities, cantons, email addresses, websites, categories, and phone display values rather than correcting only the website field. 6. **Provide a plain-text-safe output mode.** If output is intended for terminals or downstream agents, default to literal text and require an explicit option to enable Markdown links. 7. **Add adversarial tests.** Test records containing ANSI escapes, carriage returns, newlines, Markdown links, nested formatting, deceptive Unicode, and unsupported URL schemes to ensure they are rendered harmlessly. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (8)

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
ored in the `SEARCHCH_API_KEY` environment variable.

### Get an API Key

1. Visit https://search.ch/tel/api/getkey.en.html
2. Fill out the request form
3. Receive your API key via email

### Set the Environment Variable

**Temporary (current session):**
```bash
export SEARCHCH_API_KEY="your-api-key-here"
```

**Permanent (add to shell profile):**
```bash
# For bash (~/.bashrc or ~/.bash_profile)
echo 'export SEARCHCH_API_KEY="your-api-key-here"' >> ~/.bashrc
source ~/.bashrc

# For zsh (~/.zshrc)
echo 'export SEARCHCH_API_KEY="your-api-key-here"' >> ~/.zshrc
source ~/.zshrc
```

**For Clawdbot Gateway:**

Add to your gateway config or environment:
```yaml
env:
  SEARCHCH_API_KEY: "your-api-key-here"
```

## API Limits

- Without API key: Limited queries, no structured data
- With API key: More queries per day, full structured data (Atom feed)
- Maximum results per query: 200

## Troubleshooting

### "Invalid API key" error
- Verify the key is correctly set: `echo $SEARCHCH_API_KEY`
-
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Credential Access

High
Category
Privilege Escalation
Content
def get_api_key() -> str:
    """Get API key from environment variable."""
    key = os.environ.get("SEARCHCH_API_KEY")
    if not key:
        print("❌ Error: SEARCHCH_API_KEY environment variable not set", file=sys.stderr)
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill requires environment access for an API key and network access to query an external API, but it does not declare an explicit tool scope such as permissions or allowed-tools. In agent environments, missing scope declarations can cause over-broad execution privileges, reduce auditability, and make it harder to constrain or review what the skill is allowed to access.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill is explicitly designed for person lookup and reverse phone number lookup, which can expose personal contact details and facilitate profiling or harassment if used without notice or safeguards. The context makes this more sensitive, not less, because the primary purpose includes searching individuals rather than only public business records.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documentation recommends persisting an API key in shell profile files such as ~/.bashrc and ~/.zshrc without warning that these files may be broadly readable to local tools, accidentally committed, backed up, or exposed through support/debug workflows. This is not malware, but it does encourage long-lived credential storage in a less controlled location, increasing the chance of credential disclosure.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This code file performs an HTTP request to a third-party service using user-provided query and location values, but there is no confirmation prompt or user-facing disclosure at the point of transmission. Although network access is inherent to an API wrapper, the code does not visibly warn users that their inputs are being sent off-system, which matters because phone-directory lookups can contain personal data such as names, addresses, or phone numbers.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The search function sets `lang` to `de` by default, and the CLI mirrors that default, which means the tool forces a specific language unless the user notices and overrides it. The policy criteria call out locale or language defaults as violations when a specific language is imposed without explicit opt-in.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The printed status messages `Keine Treffer gefunden` and `Treffer` are always shown in German regardless of user preference. This is a natural-language policy issue because the interface language is fixed rather than selected by the user or tied to a documented, justified locale constraint.

Static analysis

No suspicious patterns detected.