Back to skill

Security audit

Linkfox 亚马逊选品

Security checks across malware telemetry and agentic risk

Overview

This Amazon research skill appears useful, but it has review-worthy risks around authenticated network calls, local data storage, and instructions to install an external companion skill.

Install only if you are comfortable with this skill sending authenticated product-research requests to its gateway and storing results locally. Before use, verify the gateway URL is fixed or allowlisted, review what data is cached or uploaded, and avoid following any runtime prompt to install a separate remote skill unless it has been independently reviewed and approved.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (92)

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
96% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
94% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
96% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
90% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
96% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
91% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
91% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
91% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
95% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
95% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
95% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
96% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
94% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
95% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
95% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
94% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
96% confidence
Finding
with urlopen(req, timeout=120) as response:

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

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=120) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
92% confidence
Finding
with urlopen(req, timeout=120) as response:

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill documents use of environment variables, filesystem writes, and network access, but does not declare permissions. This creates a transparency and least-privilege failure: an agent or reviewer may authorize a benign-looking research tool without realizing it can persist data locally and make authenticated outbound requests. In this context, the hidden capabilities matter because the skill also writes session-scoped response data to disk and relies on API credentials from the environment.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The manifest presents the skill as an Amazon research toolkit, but the documented behavior extends to local image upload to a third-party OSS, persistent local storage of API responses/session metadata, and caching/index maintenance. That mismatch can mislead users and policy engines about the actual data flows, causing sensitive inputs or commercially sensitive outputs to be uploaded or stored longer than expected. Because the skill handles product research, queries, reviews, and possibly user-provided images, the undeclared storage/upload behavior materially increases privacy and data-governance risk.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill instructs the agent to download and install another skill package from an external URL at runtime. This creates a software supply-chain risk because it expands trust to remote content outside the reviewed package, enabling execution of unvetted code or instructions if the external artifact changes or is compromised. In a product-selection skill, dynamically pulling an installer is unrelated to the core purpose and therefore more suspicious and dangerous.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
Guidance to fetch and install an external onboarding skill is unjustified by the stated purpose of Amazon product research and broadens the attack surface. Even if intended for account setup, embedding side-loading behavior in a data-query skill can be abused to introduce unreviewed capabilities, change execution flow, or coerce the agent into trust transitions not visible in the original manifest. The context makes this more dangerous because users expect a bounded analytics tool, not a bootstrapper for additional packages.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill instructs the agent to download and install another skill from a remote ZIP URL when onboarding/auth problems occur. That expands behavior beyond competitor lookup into dynamic supply-chain modification, creating a path for unreviewed code or prompt content to be introduced at runtime.

Intent-Code Divergence

Medium
Confidence
88% confidence
Finding
The docstring promises that writing to /tmp is forbidden and that a non-writable current directory will cause an error, but the implementation silently falls back to home-directory and temporary-directory storage. That mismatch can cause sensitive API responses to be written to less controlled locations than operators expect, increasing accidental exposure risk on shared systems.

Intent-Code Divergence

Medium
Confidence
89% confidence
Finding
The module documentation promises that writing to /tmp is forbidden and that non-writable current directories should cause an error, but the implementation silently falls back to home or temp directories. In a security-sensitive agent workflow, this can cause data to be persisted in less-controlled locations than operators expect, increasing the chance of unintended disclosure of API responses and session metadata.

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

No suspicious patterns detected.