Back to skill

Security audit

Amazon Voc 1.0.9

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Amazon review-analysis purpose, but its CLI can send the saved ARI API key to an arbitrary API host if ARI_BASE_URL is set.

Review this before installing if you use ARI credentials: avoid setting ARI_BASE_URL unless you fully trust the destination, and prefer a version that pins authenticated API calls to https://ari.funewa.com or validates any development override. The paid-action confirmation safeguards are clear, but the credential routing issue should be fixed by the publisher.

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
scripts/ari.py:50
Finding

Unrestricted API Origin Override Can Exfiltrate the ARI Bearer Credential

Content
View full analysis

Vulnerability Details

File Location: scripts/ari.py:50-51, with credential loading at scripts/ari.py:96-112 and authenticated network use at scripts/ari.py:261-278 and scripts/ari.py:292-308
Vulnerability Type: Arbitrary authenticated API destination / credential exfiltration
Risk Level: Medium

Vulnerable Code

python
def base_url():
    return (os.environ.get("ARI_BASE_URL") or PROD_BASE).strip().rstrip("/")

The API key may be loaded from either the environment or the protected local configuration:

python
def resolve_key():
    """Key only comes from ARI_API_KEY or the user configuration directory.

    It intentionally does not fall back to the Skill package directory: that
    location may be packaged, copied, or committed together with the Skill,
    contradicting the promise that the key is stored only in local user config.
    """
    key = os.environ.get("ARI_API_KEY", "").strip()
    if key:
        return key
    path = config_path()
    if os.path.exists(path):
        try:
            with open(path, encoding="utf-8") as fh:
                key = (json.load(fh).get("api_key") or "").strip()
            if key:
                return key
        except (OSError, json.JSONDecodeError):
            pass
    return None

Authenticated JSON requests use the unrestricted origin and attach the bearer credential:

python
def request_json(method, path, payload=None, params=None):
    query = {
        "method": method,
        "path": path,
        "params": {k: v for k, v in (params or {}).items() if v not in (None, "")},
        "payload": payload,
    }
    url = base_url() + path
    if query["params"]:
        url += "?" + urllib.parse.urlencode(query["params"], doseq=True)
    data = None if payload is None else json.dumps(payload).encode("utf-8")
    headers = {
        "Authorization": "Bearer " + require_key(),
   
...[truncated 3993 chars]
Remediation
View remediation

Remediation Suggestions

  1. Pin authenticated production requests to the expected origin:

    python
    PROD_BASE = "https://ari.funewa.com"
    

    Do not permit ordinary production execution to override this destination.

  2. If endpoint overrides are necessary for development, require an explicit development mode rather than trusting ARI_BASE_URL alone. Development mode should be disabled in distributed packages.

  3. Validate overridden URLs before any credential is loaded or attached:

    • Require the https scheme.
    • Reject embedded credentials and URL fragments.
    • Restrict hosts and ports to an explicit allowlist.
    • Reject loopback, link-local, private-network, and unapproved public destinations unless a deliberate local-development mode is active.
  4. Bind credential use to an approved origin. Construct the request first, parse its final origin, and add the Authorization header only when the origin exactly matches an approved authenticated API endpoint.

  5. Display the effective API origin and require explicit interactive confirmation before using credentials with a non-production development endpoint.

  6. Keep ARI_WEB_URL separate from API authentication. Web links must never influence the destination receiving the bearer credential.

  7. Add automated tests confirming that:

    • Unapproved hosts are rejected.
    • Plaintext HTTP is rejected.
    • Redirects cannot forward the Authorization header to another origin.
    • Production commands always authenticate only to the pinned ARI origin.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (9)

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

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The CLI allows ARI_BASE_URL from the environment to fully override the production API origin, and request_json sends the Bearer API key to that host in the Authorization header. If an attacker can influence the environment or launch context, they can redirect requests to an attacker-controlled server and exfiltrate the user's API key and all request payloads.

Content

Scanner excerpt · scripts/ari.py (reported line 278)May include surrounding context.

python
headers["Content-Type"] = "application/json"
    try:
        req = urllib.request.Request(url, data=data, headers=headers, method=method)
        with urllib.request.urlopen(req, timeout=TIMEOUT_SEC) as resp:
            note_release(resp.headers)
            raw = resp.read().decode("utf-8")
            out = json.loads(raw) if raw else {"success": True, "data": None}

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

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The SSE analysis path uses the same environment-controlled ARI_BASE_URL and transmits the Bearer API key plus analysis payloads to whatever host is configured. This makes credential exfiltration and spoofed streaming responses possible if the environment variable is manipulated.

Content

Scanner excerpt · scripts/ari.py (reported line 308)May include surrounding context.

python
try:
        req = urllib.request.Request(
            url, data=json.dumps(payload).encode("utf-8"), headers=headers, method="POST")
        with urllib.request.urlopen(req, timeout=SSE_TIMEOUT_SEC) as resp:
            note_release(resp.headers)
            content_type = resp.headers.get("Content-Type", "")
            if "text/event-stream" not in content_type:

Tainted flow: 'req' from os.environ.get (line 469, credential/environment) → urllib.request.urlopen (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/ari.py (reported line 373)May include surrounding context.

python
base_url() + "/api/v1/public/config",
            headers={"Accept": "application/json",
                     "User-Agent": user_agent()})
        with urllib.request.urlopen(req, timeout=15) as resp:
            note_release(resp.headers)
            body = json.loads(resp.read().decode("utf-8"))
    except Exception:

Tainted flow: 'req' from os.environ.get (line 469, credential/environment) → urllib.request.urlopen (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/ari.py (reported line 470)May include surrounding context.

python
headers["Content-Type"] = "application/json"
    try:
        req = urllib.request.Request(base_url() + path, data=data, headers=headers, method=method)
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            note_release(resp.headers)
            out = json.loads(resp.read().decode("utf-8"))
            if isinstance(out, dict):

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill instructs use of a local CLI that can access environment variables, write files, invoke shell commands, and make network requests, yet the manifest declares no explicit tool scope or permission boundaries. In practice this creates excessive implicit capability: an agent may run setup/configuration flows, persist secrets locally, and contact external services without a least-privilege declaration, increasing the blast radius if the skill is misused or its instructions are subverted.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The trigger phrases are broad enough to match common discussion about reviews, feedback, VOC, and comment analysis, which can cause the skill to activate in conversations where the user did not intend to invoke this networked, credit-consuming workflow. Because the skill can lead to external API use and potential paid actions after follow-up steps, overbroad routing increases the chance of unintended data access, confusing behavior, or unnecessary spend.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The analysis arguments set --language to zh by default, which forces a specific output language unless the user explicitly overrides it. This is a natural-language policy concern because the tool does not prompt for or otherwise obtain language preference before selecting a locale.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The deepdive subcommand also defaults --language to zh, causing generated analysis to use Chinese unless the user notices and changes the option. Because this is a user-facing locale choice, hardcoding the default without opt-in violates the language/locale policy criteria.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

The text says users can directly submit requests in Chinese, but the document does not indicate that other languages are supported or that Chinese is optional. This can be read as imposing a language expectation without user opt-in, which matches the language/locale policy concern.

Content

No source excerpt is available for this finding.