Back to skill

Security audit

AEO Prompt Question Finder

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed keyword-research helper with an optional credentialed search-volume feature, and I did not find hidden persistence, destructive behavior, or deception.

Install this only if you are comfortable with outbound keyword queries. Normal use contacts Google Autocomplete; using --volume also contacts DataForSEO and may use local DataForSEO credentials from environment variables or macOS Keychain. Avoid --volume for sensitive client, legal, medical, or confidential research terms unless that third-party sharing is acceptable.

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
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (14)

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

Critical
Category
Data Flow
Content
params = urllib.parse.urlencode({"client": "firefox", "q": query})
    url = f"{BASE_URL}?{params}"
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=10) as resp:
        data = json.loads(resp.read().decode())
    return data[1] if len(data) > 1 else []
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
})

        try:
            with urllib.request.urlopen(req, timeout=30) as resp:
                result = json.loads(resp.read().decode())
            if result.get("tasks"):
                for task in result["tasks"]:
Confidence
95% confidence
Finding
When `--volume` is used, the script retrieves DataForSEO credentials from environment variables or the macOS Keychain and transmits them in a Basic Authorization header to a third-party API. This is intentional for authentication, but it still expands the skill's privilege and external data exposure beyond basic autocomplete collection, increasing the risk of unintended secret use or data leakage to an unnecessary service.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The stated purpose presents the skill as a simple Google Autocomplete helper, but the behavior also includes authenticated third-party API use and retrieval of stored credentials from macOS Keychain or environment variables. This is dangerous because users may invoke the skill expecting anonymous suggestion lookups while unintentionally sending topics to another provider under local credentials.

Credential Access

High
Category
Privilege Escalation
Content
return data[1] if len(data) > 1 else []


def get_keychain(service: str) -> str:
    """Read a value from macOS Keychain."""
    try:
        return subprocess.check_output(
Confidence
98% confidence
Finding
The function is explicitly designed to read secrets from the macOS Keychain. In an agent skill, local credential access is sensitive by default, especially when the skill's primary purpose does not require privileged secret retrieval to perform basic autocomplete queries.

Credential Access

High
Category
Privilege Escalation
Content
def get_keychain(service: str) -> str:
    """Read a value from macOS Keychain."""
    try:
        return subprocess.check_output(
            ["security", "find-generic-password", "-s", service, "-w"],
Confidence
98% confidence
Finding
The use of the macOS Keychain via the `security` tool indicates direct access to stored credentials. Even without shell injection, this behavior is security-sensitive and expands the skill from harmless query collection into privileged secret retrieval.

Credential Access

High
Category
Privilege Escalation
Content
def fetch_search_volumes(keywords: list[str], location_code: int = 2840, language_code: str = "en") -> dict[str, int | None]:
    """Fetch avg monthly search volumes from DataForSEO Keywords Data API.
    Returns {keyword: volume} dict. Volume is None if not found."""
    login = os.environ.get("DATAFORSEO_LOGIN") or get_keychain("dataforseo-login")
    password = os.environ.get("DATAFORSEO_PASSWORD") or get_keychain("dataforseo-password")
    if not login or not password:
        print("WARNING: DataForSEO credentials not found. Skipping volume lookup.", file=sys.stderr)
Confidence
99% confidence
Finding
At this line the script automatically pulls the DataForSEO login from either environment variables or the Keychain. Automatic secret consumption in a skill increases the chance of unintended credential use and makes the behavior more dangerous than its autocomplete-focused description suggests.

Credential Access

High
Category
Privilege Escalation
Content
"""Fetch avg monthly search volumes from DataForSEO Keywords Data API.
    Returns {keyword: volume} dict. Volume is None if not found."""
    login = os.environ.get("DATAFORSEO_LOGIN") or get_keychain("dataforseo-login")
    password = os.environ.get("DATAFORSEO_PASSWORD") or get_keychain("dataforseo-password")
    if not login or not password:
        print("WARNING: DataForSEO credentials not found. Skipping volume lookup.", file=sys.stderr)
        return {}
Confidence
99% confidence
Finding
This line automatically retrieves the DataForSEO password from environment variables or the Keychain, enabling credentialed third-party access without strong user awareness. In the context of a simple research helper, that is an unnecessary increase in sensitivity and attack surface.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill documentation declares no explicit tool scope even though the described behavior requires shell execution, outbound network access, and access to environment variables or credentials. That mismatch can cause the skill to run with broader capabilities than users expect, reducing transparency and making accidental misuse or over-privileged execution more likely.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The documentation does not warn users that enabling --volume sends their query topics to DataForSEO and uses stored credentials from Keychain or environment variables. This creates a privacy and consent risk, particularly if topics contain sensitive business terms, client data, or research subjects that users believed would only be sent to Google Autocomplete.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The script reads credentials from environment variables and the macOS Keychain for a third-party SEO API even though the core skill description does not justify credential access. In agent settings, automatic secret discovery is dangerous because it broadens access to local sensitive material and can be surprising to operators who expect a simple public autocomplete lookup.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def get_keychain(service: str) -> str:
    """Read a value from macOS Keychain."""
    try:
        return subprocess.check_output(
            ["security", "find-generic-password", "-s", service, "-w"],
            stderr=subprocess.DEVNULL
        ).decode().strip()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The skill's stated purpose is finding Google Autocomplete questions, but it also performs optional enrichment by calling a separate commercial API. That scope expansion is risky because it causes additional outbound transmission of collected keywords and can trigger authenticated access not obviously expected from the skill description.

External Transmission

Medium
Category
Data Exfiltration
Content
batch = keywords[i:i + DATAFORSEO_BATCH]
        payload = [{"keywords": batch, "location_code": location_code, "language_code": language_code}]
        data = json.dumps(payload).encode()
        url = "https://api.dataforseo.com/v3/keywords_data/google_ads/search_volume/live"

        auth = base64.b64encode(f"{login}:{password}".encode()).decode()
        req = urllib.request.Request(url, data=data, method="POST", headers={
Confidence
94% confidence
Finding
This code sends collected keywords to `api.dataforseo.com`, an external third party, for enrichment. In context, the transmission is not inherently malicious, but it increases privacy and supply-chain risk because user research terms and associated metadata leave the local environment for a service not central to the advertised autocomplete-only function.

Natural-Language Policy Violations

Low
Confidence
83% confidence
Finding
The documentation states `--lang en` with `default: en`, which establishes English as the default locale behavior. Under the policy, locale constraints should either be user-selectable with clear opt-in or explicitly justified as region-specific; this file does not provide that justification.

Static analysis

No suspicious patterns detected.