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## 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
## 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
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.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
# 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)
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
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):
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
args = p.parse_args()

    try:
        market = getattr(Market, args.market)
    except AttributeError:
        print(f"Invalid market: {args.market}", file=sys.stderr)
        return 2
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
args = p.parse_args()

    try:
        market = getattr(Market, args.market)
    except AttributeError:
        print(f"Invalid market: {args.market}", file=sys.stderr)
        return 2
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

No suspicious patterns detected.