Back to skill

Security audit

Gradient Inference

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its DigitalOcean AI purpose, but its image-saving and pricing-cache code have unsafe local file-write behavior that should be reviewed before installation.

Review this skill before installing. Use it only if you are comfortable sending selected prompts and the Gradient API key to DigitalOcean, avoid enabling --cache for sensitive prompts, run it as a non-privileged user, and prefer a sandboxed working directory for image outputs until the path validation, URL download controls, and /tmp cache handling are fixed.

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

Error
Location
scripts/gradient_image.py:91
Finding

Output Path Validation Can Be Bypassed to Write Outside the Working Directory

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gradient_image.py:106
Finding

Unrestricted Server-Supplied Image URL Enables SSRF and Unbounded Downloads

Content
View full analysis
Remediation
View remediation
MAX_IMAGE_BYTES: raise ValueError("Image exceeds maximum permitted size") handle.write(chunk) ``` - Validate image signatures after download rather than relying solely on the HTTP content type. - Combine these controls with the corrected output-path validation described in the separate finding. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gradient_pricing.py:24
Finding

Predictable Shared Temporary Cache Enables Cache Poisoning and Symlink-Based File Overwrite

Content
View full analysis
Optional[dict]: """Read cached pricing data if it exists and is fresh.""" if not CACHE_PATH.exists(): return None try: data = json.loads(CACHE_PATH.read_text()) if time.time() - data.get("cached_at", 0) < CACHE_TTL_SECONDS: return data except (json.JSONDecodeError, KeyError): pass return None def _write_cache(data: dict) -> None: """Write pricing data to the cache file.""" data["cached_at"] = time.time() try: CACHE_PATH.write_text(json.dumps(data, indent=2)) except OSError: pass # Best-effort caching ``` ### Technical Analysis The pricing cache uses a globally predictable filename in the shared `/tmp` directory. The code neither verifies ownership and file type nor prevents symbolic-link traversal. `Path.write_text()` follows symbolic links and overwrites existing files. A local attacker may pre-create `/tmp/gradient_pricing_cache.json` as a symbolic link to another file writable by the account that runs the Skill. When live pricing is successfully fetched, the cache write follows that link and overwrites the target with JSON data. The read path also trusts any syntactically valid JSON placed at that location. An attacker can supply forged pricing records with a sufficiently recent or future `cached_at` value, causing the application to display attacker-controlled pricing data for up to the cache lifetime or longer when a future timestamp is used. ### Attack Path **Cache poisoning:** 1. A local attacker creates `/tmp/gradient_pricing_cache.json`. 2. The file contains valid JSON, fabricated model prices, and a future `cached_at` value. 3. A victim runs the pr ...[truncated 963 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
SKILL.md:14
Finding

Unpinned Third-Party Dependencies Reduce Supply-Chain Integrity

Content
View full analysis
=2.31.0 - beautifulsoup4>=4.12.0 ``` ### Technical Analysis The dependencies specify only minimum versions. There are no exact version pins, upper bounds, package hashes, or lock file. A future installation can therefore resolve to package releases that were not part of this audit. This is not evidence that either named dependency is currently malicious. The security concern is that the effective installed code can change without any change to the reviewed Skill package. A compromised package release, compromised package index, or unexpectedly incompatible future release could execute code during installation or when imported. ### Attack Path 1. A user installs the Skill dependencies at a later time. 2. The package resolver selects the newest versions satisfying the broad `>=` constraints. 3. A selected release contains compromised or otherwise unsafe code, or the configured package index is malicious. 4. That code executes during package installation or when the scripts import the dependency. 5. It receives the permissions of the account installing or running the Skill. ### Impact Assessment Compromised dependency code would execute with the privileges of the installer or runtime account and could access the Skill's process environment, including `GRADIENT_API_KEY` when present. It could also access files and network resources available to that account. The present files contain no evidence that `requests` or `beautifulsoup4` are malicious; this finding concerns reproducibility and future supply-chain exposure. ]]>
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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
Findings (14)

Tainted flow: 'headers' from os.environ.get (line 123, credential/environment) → requests.post (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/gradient_chat.py (reported line 71)May include surrounding context.

python
"max_tokens": max_tokens,
        }

        resp = requests.post(CHAT_COMPLETIONS_URL, headers=headers, json=payload, timeout=60)
        resp.raise_for_status()

        data = resp.json()

Tainted flow: 'headers' from os.environ.get (line 123, credential/environment) → requests.post (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/gradient_chat.py (reported line 138)May include surrounding context.

python
if store:
            payload["store"] = True

        resp = requests.post(RESPONSES_URL, headers=headers, json=payload, timeout=60)
        resp.raise_for_status()

        data = resp.json()

Tainted flow: 'headers' from os.environ.get (line 59, credential/environment) → requests.post (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/gradient_image.py (reported line 71)May include surrounding context.

python
"size": size,
        }

        resp = requests.post(IMAGE_GENERATION_URL, headers=headers, json=payload, timeout=120)
        resp.raise_for_status()

        data = resp.json()

Tainted flow: 'headers' from os.environ.get (line 44, credential/environment) → requests.get (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/gradient_models.py (reported line 49)May include surrounding context.

python
"Content-Type": "application/json",
        }

        resp = requests.get(f"{INFERENCE_BASE_URL}/models", headers=headers, timeout=15)
        resp.raise_for_status()

        data = resp.json()

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The code accurately supports part of the description: it is OpenAI-compatible in style, calls DigitalOcean Gradient serverless inference, supports chat completions, supports the Responses API, and exposes prompt caching through the store flag. However, the declared description also claims capabilities to discover available models and pricing and to generate images. None of those behaviors appear in this code chunk: there are no endpoints or functions for listing models, checking prices, or creating images. The primary purpose is narrower than declared, so this is a description/behavior mismatch.

Content

No source excerpt is available for this finding.

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · SKILL.md (reported line 64)May include surrounding context.

Direct API call:

bash
curl -s https://inference.do-ai.run/v1/models \
  -H "Authorization: Bearer $GRADIENT_API_KEY" | python3 -m json.tool

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises capabilities that include environment access, file read/write, and network use, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an avoidable trust gap: a user or orchestrator cannot easily constrain the skill to the minimum required privileges, increasing the blast radius if the scripts are modified, abused, or invoked in an unexpected context.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 90)May include surrounding context.

Direct API call:

bash
curl -s https://inference.do-ai.run/v1/chat/completions \
  -H "Authorization: Bearer $GRADIENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 162)May include surrounding context.

Direct API call:

bash
curl -s https://inference.do-ai.run/v1/images/generations \
  -H "Authorization: Bearer $GRADIENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/gradient_chat.py (reported line 71)May include surrounding context.

python
"max_tokens": max_tokens,
        }

        resp = requests.post(CHAT_COMPLETIONS_URL, headers=headers, json=payload, timeout=60)
        resp.raise_for_status()

        data = resp.json()

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/gradient_chat.py (reported line 138)May include surrounding context.

python
if store:
            payload["store"] = True

        resp = requests.post(RESPONSES_URL, headers=headers, json=payload, timeout=60)
        resp.raise_for_status()

        data = resp.json()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The --cache option enables prompt caching/storage on the remote service, which can cause user prompts to be retained or reused by the provider without any explicit runtime warning or consent step. In an agent skill context, prompts may contain sensitive user or workspace data, making silent retention a privacy and data-handling risk.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/gradient_image.py (reported line 71)May include surrounding context.

python
"size": size,
        }

        resp = requests.post(IMAGE_GENERATION_URL, headers=headers, json=payload, timeout=120)
        resp.raise_for_status()

        data = resp.json()

Tainted flow: 'data' from pathlib.Path.read_text (line 171, file read) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/gradient_pricing.py (reported line 183)May include surrounding context.

python
"""Write pricing data to the cache file."""
    data["cached_at"] = time.time()
    try:
        CACHE_PATH.write_text(json.dumps(data, indent=2))
    except OSError:
        pass  # Best-effort caching

Static analysis

No suspicious patterns detected.