Back to skill

Security audit

Tvscreener

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent TradingView market-data helper, but its shell wrappers can silently install or upgrade an unpinned Python package at runtime.

Install only in an isolated virtual environment, review or remove the auto-install lines before running shell wrappers, and prefer a pinned requirements file for tvscreener. The skill appears focused on market-data lookup, but users should not let it modify a shared Python environment automatically.

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

T08 · Insecure Dependencies

Warning
Location
scripts/run_query.sh:13
Finding

Unpinned Automatic Runtime Dependency Installation

Content
View full analysis

Vulnerability Details

File Location: scripts/run_query.sh, line 13
Vulnerability Type: Supply-chain exposure through automatic installation of an unpinned dependency
Risk Level: Medium

Vulnerable Code

bash
"$PYTHON_BIN" - <<'PY' >/dev/null 2>&1 || "$PYTHON_BIN" -m pip install -q -U tvscreener
from tvscreener import Market, StockField, StockScreener
PY

The same unsafe installation pattern also appears at scripts/test_markets.sh:13. The unpinned installation command is documented at SKILL.md:13 and SKILL.md:56:

bash
python3 -m pip install -U tvscreener

Technical Analysis

The runtime wrapper automatically executes pip install -U tvscreener whenever the dependency import check fails. No exact version, package hash, lockfile, trusted package index, or integrity verification is specified. The -U option requests the newest eligible release, so the installed code can change independently of this audited project.

Package installation and subsequent import execute third-party code under the privileges of the user running the skill. Consequently, a compromised package release, package-index account, dependency, or configured package source could turn an otherwise routine market query into arbitrary local code execution. Suppressing installation output with -q and redirecting the import check output also reduces visibility into this environment-changing behavior.

This is a supply-chain weakness rather than evidence that the current tvscreener package is malicious.

Attack Path

  1. An attacker compromises the tvscreener distribution channel, a transitive dependency, or a package source configured in the victim's pip environment.
  2. The attacker publishes a malicious release that satisfies the unconstrained package request.
  3. A user runs scripts/run_query.sh or scripts/test_markets.sh in an environment where the import check fails.
  4. The shell script automatically invokes pip install -q -U tvscreener ...[truncated 793 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove automatic package installation from runtime and test scripts. Fail with a clear message when the dependency is unavailable.
  2. Pin tvscreener and all transitive dependencies to reviewed versions in a lockfile or fully pinned requirements file.
  3. Require package hashes, for example by using pip install --require-hashes -r requirements.txt.
  4. Configure an explicit trusted package index or an internally controlled artifact repository rather than inheriting arbitrary pip source configuration.
  5. Install dependencies during a separate, explicit setup phase inside an isolated virtual environment.
  6. Avoid -U in operational scripts so dependency changes occur only through a reviewed update process.
  7. Preserve installation and dependency-resolution output in setup logs so users can review environment changes.
  8. Apply the same changes to scripts/test_markets.sh and update the installation instructions in SKILL.md.
Vulnerability Patterns
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (5)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script silently installs or upgrades the Python package tvscreener at runtime if import fails, without prompting the user or pinning a version. This creates a supply-chain and integrity risk: execution behavior can change unexpectedly, and a compromised or malicious package/version from the package index could be fetched and executed in the user's environment.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/custom_query.py (reported line 18)May include surrounding context.

python
# Supports: PRICE or RELATIVE_STRENGTH_INDEX_14|60
    if "|" in token:
        name, interval = token.split("|", 1)
        base = getattr(StockField, name)
        return base.with_interval(interval)
    return getattr(StockField, token)

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/custom_query.py (reported line 20)May include surrounding context.

python
name, interval = token.split("|", 1)
        base = getattr(StockField, name)
        return base.with_interval(interval)
    return getattr(StockField, token)


def parse_value(v: str, force_string: bool = False):

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/custom_query.py (reported line 69)May include surrounding context.

python
args = p.parse_args()

    try:
        market = getattr(Market, args.market)
    except AttributeError:
        print(f"Invalid market: {args.market}", file=sys.stderr)
        return 2

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/query_symbol.py (reported line 17)May include surrounding context.

python
args = p.parse_args()

    try:
        market = getattr(Market, args.market)
    except AttributeError:
        print(f"Invalid market: {args.market}", file=sys.stderr)
        return 2

Static analysis

No suspicious patterns detected.