Back to skill

Security audit

Contact Finder

Security checks for vulnerabilities and agentic risk

Overview

This contact-finding skill is purpose-aligned, but it needs Review because it sends personal/professional lookup data to third parties and has weak safeguards around result integrity and API-key logging.

Install only if you are comfortable sending lookup targets and search snippets to SerpAPI or Brave and OpenAI. Treat returned emails as leads, not verified contacts; independently confirm addresses before use, avoid entering sensitive targets without a lawful basis, use a virtual environment with pinned dependencies, and remove API-key prefixes from logs before operational use.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/find_contacts.py:130
Finding
Prompt Injection Through Untrusted Search Result Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/find_contacts.py:130-153` **Vulnerability Type**: Prompt injection through untrusted external content **Risk Level**: Medium ### Vulnerable Code ```python combined = "\n\n".join(snippets[:15]) # limit tokens name_hint = f" for person named '{name}'" if name else "" prompt = f"""Extract professional contact information{name_hint} from the following search snippets. Focus on domain: {domain} For each contact found, return a JSON array with objects containing: - "email": email address (string or null) - "linkedin": LinkedIn URL (string or null) - "title": job title (string or null) - "name": person name (string or null) - "confidence": "high" if email found directly in text, "medium" if inferred from context, "low" if uncertain Only return valid JSON array. If nothing found, return []. SNIPPETS: {combined}""" try: response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.1, max_tokens=1000 ) ``` ### Technical Analysis Search result titles, descriptions, and URLs originate from external websites and are therefore attacker-controlled. The application concatenates this content directly into the same user message that contains the extraction instructions. No strong trust boundary distinguishes application instructions from the untrusted snippets. An attacker can publish indexed content containing instructions such as requests to disregard the extraction task and return fabricated JSON. The language model may follow those embedded instructions because they appear in its active prompt. The response is accepted after only a permissive regular-expression search and JSON parsing. There is no strict output schema, field allowlist, semantic validation, or independent confirmation that returned values appeared in the source snippets. ### Attack Path 1. An attacker publishes a page likely to ...[truncated 1005 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Place extraction policy in a system message and explicitly state that snippet content is untrusted data that must never be treated as instructions. - Delimit each snippet using a structured representation such as JSON rather than interpolating it into free-form instructions. - Use OpenAI structured output or JSON-schema enforcement with an exact field allowlist and strict types. - Reject output containing unexpected keys, invalid email addresses, unsupported URL schemes, or values that cannot be traced to a supplied snippet. - Independently verify extracted email addresses and profile URLs before assigning confidence. - Consider processing snippets separately to reduce the effect of one malicious result on all extracted contacts. - Add adversarial tests containing instructions such as “ignore previous instructions” to verify that the extraction boundary is effective. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:40
Finding
Unpinned Third-Party Dependencies in Installation Instructions<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:40-44` **Vulnerability Type**: Unpinned runtime dependencies **Risk Level**: Medium ### Vulnerable Code ```markdown ## Setup ```bash pip3 install openai requests ``` ``` ### Technical Analysis The documented installation command retrieves the latest available versions of `openai` and `requests` without version constraints, integrity hashes, or a lockfile. Consequently, the code installed and executed by users can change after this skill has been reviewed. This practice increases exposure to compromised upstream releases, malicious dependency substitution through an incorrectly configured package index, and compatibility-breaking updates. Python packages may execute code during installation or when imported by the skill. The finding concerns dependency integrity rather than evidence that the currently named packages are malicious. ### Attack Path 1. A user follows the documented setup command. 2. `pip` resolves packages using the user's configured package indexes. 3. A compromised, substituted, or unexpectedly changed package version is selected. 4. Package-controlled code executes during installation or subsequent import. 5. That code runs with the permissions of the user invoking `pip` or the contact-finder script. ### Impact Assessment A compromised dependency could obtain all permissions available to the installing user, including reading accessible files, using environment variables such as API credentials, making network requests, and modifying user-owned files. If installation is performed with administrator privileges, the potential impact expands accordingly. The project itself does not request elevated privileges, so actual scope depends on how the user performs installation. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin all direct dependencies to reviewed versions. - Provide a requirements or lock file that also fixes transitive dependency versions. - Include cryptographic hashes and install with hash verification, for example through `pip install --require-hashes`. - Document installation inside a dedicated virtual environment rather than the system Python environment. - Explicitly document the trusted package index and avoid untrusted or mixed indexes. - Use automated dependency vulnerability and update monitoring. - Test and review dependency updates before changing pinned versions. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/find_contacts.py:90
Finding
Partial Brave API Key Disclosure in Error Logs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/find_contacts.py:90-104` **Vulnerability Type**: Sensitive credential material exposed through logging **Risk Level**: Low ### Vulnerable Code ```python def brave_search(query: str) -> list[dict]: """Fallback: Search via Brave Search API.""" for key in BRAVE_API_KEYS: try: resp = requests.get( "https://api.search.brave.com/res/v1/web/search", params={"q": query, "count": 10}, headers={"Accept": "application/json", "X-Subscription-Token": key.strip()}, timeout=15 ) if resp.status_code == 200: results = resp.json().get("web", {}).get("results", []) # Normalize to SerpAPI format return [{"title": r.get("title", ""), "link": r.get("url", ""), "snippet": r.get("description", "")} for r in results] except Exception as e: print(f"[WARN] Brave search error ({key[:10]}...): {e}", file=sys.stderr) ``` ### Technical Analysis When a Brave request raises an exception, the application writes the first ten characters of the active API key to standard error. Although this is not the complete credential, secret prefixes should not be included in logs. Standard error may be captured by shell history tooling, CI systems, orchestration platforms, support bundles, or centralized logging infrastructure. Anyone with access to those logs can recover part of the credential and correlate it across systems or incidents. ### Attack Path 1. An attacker or operational failure causes the Brave API request to raise an exception, such as through a network or TLS failure. 2. The exception handler executes. 3. The first ten API-key characters are written to standard error. 4. Standard error is retained in a terminal transcript, CI artifact, service log, or monitoring platform. 5. A party with access to those logs obtains the credential prefix. ...[truncated 461 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove all API-key material from error messages, including partial prefixes and suffixes. - Identify keys using a non-secret ordinal such as `Brave key #1`. - If correlation is required, use a short one-way fingerprint generated with an appropriate keyed mechanism rather than displaying token characters. - Sanitize exception messages before forwarding them to shared logs. - Configure log retention and access controls according to the sensitivity of operational metadata. - Rotate affected credentials if their prefixes have already been retained in broadly accessible logs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/find_contacts.py:205
Finding
Unsafe Substring-Based Email Domain Validation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/find_contacts.py:205-214` **Vulnerability Type**: Improper validation of model-generated email addresses **Risk Level**: Medium ### Vulnerable Code ```python ai_contacts = extract_contacts_with_openai(all_snippets, domain, name) for c in ai_contacts: # Validate email domain email = c.get("email", "") if email and domain.lower() in email.lower(): c["confidence"] = "high" elif email: c["confidence"] = "medium" c["source"] = "serpapi+openai" contacts.append(c) ``` ### Technical Analysis The application treats an email as belonging to the requested domain whenever the target domain occurs anywhere in the email string. Substring containment does not validate email syntax or compare the actual domain portion of the address. For example, when the requested domain is `acme.com`, values such as `user@evilacme.com` or malformed text containing `acme.com` can satisfy the condition. The code then overrides the model-provided confidence and assigns `high` confidence. Because the email is derived from untrusted search content through a language model, an attacker can deliberately construct a lookalike address that passes this check. ### Attack Path 1. An attacker publishes content containing a lookalike address such as `contact@evilacme.com` for the target `acme.com`. 2. The content appears in a SerpAPI or Brave Search result. 3. OpenAI extracts the lookalike value as an email address. 4. The expression `domain.lower() in email.lower()` evaluates to true. 5. The application changes the confidence to `high`. 6. The user is presented with an attacker-controlled address that appears strongly validated. ### Impact Assessment The vulnerability can misdirect email to an attacker-controlled domain, increase the credibility of poisoned contact information, and support impersonation or phishing attacks. It compromises result integrity but does not directly grant local exe ...[truncated 219 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Parse the address with a standards-aware email validation library. - Extract and normalize the domain after the final `@` separator. - Compare the normalized domain exactly against the normalized requested domain. - If subdomains are allowed, enforce an explicit boundary-aware policy such as equality or a suffix match preceded by a dot. - Reject addresses with invalid syntax, display-name injection, control characters, or multiple addresses. - Do not assign high confidence solely from domain equality; require direct source evidence or independent verification. - Preserve provenance showing the exact snippet from which each address was extracted. - Add tests for lookalike values such as `user@evilacme.com`, `user@acme.com.evil.test`, and malformed strings containing the target domain. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

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

Critical
Category
Data Flow
Content
"engine": "google"
    }
    try:
        resp = requests.get(url, params=params, timeout=15)
        resp.raise_for_status()
        data = resp.json()
        return data.get("organic_results", [])
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill declares no explicit tool scope even though its documented behavior requires network access and use of environment-provided API keys. Without clear permissions or allowed-tools constraints, an agent platform may grant broader capabilities than users expect, increasing the risk of unintended external requests or misuse of secrets during execution.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill is designed to search for, infer, and transmit professional contact information to third-party services including SerpAPI and OpenAI, but it does not warn users that personal/professional data may be sent off-platform. This creates privacy, compliance, and user-consent risks, especially when searching named individuals or generating guessed email addresses that may constitute personal data processing.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Notes

- Always verify emails before sending (use SMTP check or verification API)
- GDPR: Only use publicly available data; do not store without consent
- Rate limit: SerpAPI free = 100 searches/month; paid plans available
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script transmits search queries containing a person's name, company, and domain to external search providers, which can expose investigative intent and personal data to third parties. In this skill's context, the behavior is core to functionality, but it is still a genuine privacy issue because users are not clearly warned that this identifying information leaves the local environment.

External Transmission

Medium
Category
Data Exfiltration
Content
for key in BRAVE_API_KEYS:
        try:
            resp = requests.get(
                "https://api.search.brave.com/res/v1/web/search",
                params={"q": query, "count": 10},
                headers={"Accept": "application/json", "X-Subscription-Token": key.strip()},
                timeout=15
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
92% confidence
Finding
Search snippets may contain personal data, employer details, email addresses, and profile information, and the code forwards up to 15 snippets to OpenAI without consent gating, minimization, or a clear privacy notice. This creates a real data-sharing risk because third-party model providers receive potentially sensitive personal or company-related information during processing.

Static analysis

No suspicious patterns detected.