Mostly safe โ a couple of notes worth reading.
Scanned 5/3/2026, 6:17:37 PMยทCached resultยทFast Scanยท45 rulesยทHow we decide โ
AIVSS Score
Low
Severity Breakdown
0
critical
1
high
11
medium
7
low
MCP Server Information
Findings
This package receives a B grade with a safety score of 83/100 but carries moderate risk due to 11 medium-severity findings concentrated in readiness and server configuration issues. The primary concerns involve improper error handling that could leak sensitive information and several configuration gaps that may affect deployment security. While no critical vulnerabilities were detected, you should address the medium-severity readiness and configuration issues before production use.
No known CVEs found for this package or its dependencies.
Scan Details
Want deeper analysis?
Fast scan found 19 findings using rule-based analysis. Upgrade for LLM consensus across 5 judges, AI-generated remediation, and cross-file taint analysis.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnโt shipped yet.
19 of 19 findings
19 findings
Non-cryptographic random source used for security-sensitive value. random.random/randint, Math.random, and numpy.random are predictable; tokens, session IDs, and passwords generated this way are guessable.
Evidence
| 59 | if not is_proxy_configured(): |
| 60 | return None |
| 61 | c = _cfg() |
| 62 | session_id = random.randint(c["min"], c["max"]) |
| 63 | return f"http://{c['prefix']}-{session_id}:{c['password']}@{c['host']}:{c['port']}" |
Remediation
Use the secrets module (Python: secrets.token_urlsafe, secrets.choice), crypto.randomBytes (Node), or crypto.getRandomValues (browser). Reserve random.* for simulations, games, and ML sampling.
Service binds to 0.0.0.0 โ all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 39 | # Run the MCP server over streamable-http (ideal for Docker/remote deployments) |
| 40 | ENTRYPOINT ["tradingview-mcp"] |
| 41 | CMD ["streamable-http", "--host", "0.0.0.0", "--port", "8000"] |
Remediation
Bind to 127.0.0.1 for local-only access. If cross-host access is truly required, put the service behind an authenticated reverse proxy rather than exposing it on 0.0.0.0.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 92 | "timestamp": datetime.now(timezone.utc).isoformat(), |
| 93 | } |
| 94 | except Exception as e: |
| 95 | return {"symbol": symbol.upper(), "error": str(e), "source": "Yahoo Finance"} |
| 96 | |
| 97 | |
| 98 | def get_prices_bulk(symbols: list[str]) -> list[dict]: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 172 | except Exception as e: |
| 173 | conn.rollback() |
| 174 | return {"error": f"Database error during trade: {str(e)}"} |
| 175 | finally: |
| 176 | conn.close() |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure โ useful recon for attackers.
Evidence
| 332 | "volume": volume, |
| 333 | } |
| 334 | except Exception as exc: |
| 335 | return {"detected": False, "score": 0, "error": str(exc)} |
| 336 | |
| 337 | |
| 338 | def fetch_multi_timeframe_patterns( |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 35 | # Health check |
| 36 | HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ |
| 37 | CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 |
| 38 | |
| 39 | # Run the MCP server over streamable-http (ideal for Docker/remote deployments) |
| 40 | ENTRYPOINT ["tradingview-mcp"] |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 47 | - uses: docker/setup-buildx-action@v3 |
| 48 | |
| 49 | # Only log in when we actually intend to push (skip on pull_request). |
| 50 | - uses: docker/login-action@v3 |
| 51 | if: github.event_name != 'pull_request' |
| 52 | with: |
| 53 | registry: ghcr.io |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 40 | packages: write # needed to push to ghcr.io/<owner>/<repo> |
| 41 | |
| 42 | steps: |
| 43 | - uses: actions/checkout@v4 |
| 44 | |
| 45 | - uses: docker/setup-qemu-action@v3 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 42 | steps: |
| 43 | - uses: actions/checkout@v4 |
| 44 | |
| 45 | - uses: docker/setup-qemu-action@v3 |
| 46 | |
| 47 | - uses: docker/setup-buildx-action@v3 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 44 | - uses: docker/setup-qemu-action@v3 |
| 45 | |
| 46 | - uses: docker/setup-buildx-action@v3 |
| 47 | |
| 48 | # Only log in when we actually intend to push (skip on pull_request). |
| 49 | - uses: docker/login-action@v3 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 55 | password: ${{ secrets.GITHUB_TOKEN }} |
| 56 | |
| 57 | - id: meta |
| 58 | uses: docker/metadata-action@v5 |
| 59 | with: |
| 60 | images: ghcr.io/${{ github.repository }} |
| 61 | # Tag logic: |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 71 | type=sha,prefix=sha- |
| 72 | type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') || startsWith(github.ref, 'refs/tags/v') }} |
| 73 | |
| 74 | - uses: docker/build-push-action@v6 |
| 75 | with: |
| 76 | context: . |
| 77 | platforms: linux/amd64,linux/arm64 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 656 | if f.endswith(".txt") |
| 657 | ] |
| 658 | if exchanges: |
| 659 | return f"Available exchanges: {', '.join(sorted(exchanges))}" |
| 660 | except Exception: |
| 661 | pass |
| 662 | return "Common exchanges: KUCOIN, BINANCE, BYBIT, MEXC, BITGET, OKX, COINBASE, GATEIO, HUOBI, BITFINEX, KRAKEN, BITSTAMP, BIST, EGX, NASDAQ, TWSE, TPEX" |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 29 | try: |
| 30 | from dotenv import load_dotenv |
| 31 | _env_path = os.path.join(os.path.dirname(__file__), "../../../../.env") |
| 32 | load_dotenv(dotenv_path=_env_path, override=False) |
| 33 | except ImportError: |
| 34 | pass |
| 35 | |
| 36 | |
| 37 | # โโโ Read config from env โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 231 | "method": "multi-timeframe", |
| 232 | "total_found": len(results), |
| 233 | "data": results[:limit], |
| 234 | } |
| 235 | except Exception: |
| 236 | pass # Fall through to single-timeframe fallback |
| 237 | |
| 238 | return scan_advanced_candle_patterns_single_tf(exchange, symbols, base_timeframe, pattern_length, min_size_increase, limit) |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 53 | data = None |
| 54 | try: |
| 55 | with urllib.request.urlopen(req, timeout=15) as resp: |
| 56 | data = json.loads(resp.read().decode("utf-8")) |
| 57 | except Exception: |
| 58 | pass |
| 59 | |
| 60 | if data is None: |
| 61 | try: |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 686 | else: |
| 687 | try: |
| 688 | mcp.settings.host = args.host |
| 689 | mcp.settings.port = args.port |
| 690 | except Exception: |
| 691 | pass |
| 692 | mcp.run(transport="streamable-http") |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 1022 | if h is not None and ll is not None and h > ll: |
| 1023 | swing_high = float(h) |
| 1024 | swing_low = float(ll) |
| 1025 | swing_source = f"screener ({lookback} period high/low)" |
| 1026 | except Exception: |
| 1027 | pass |
| 1028 | |
| 1029 | try: |
| 1030 | analysis = get_multiple_analysis(screener=screener, interval=timeframe, symbols=[full_symbol]) |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 51 | # Filter out None values (can happen for incomplete candles) |
| 52 | valid_closes = [c for c in closes if c is not None] |
| 53 | if len(valid_closes) >= 2: |
| 54 | return valid_closes[-2] |
| 55 | except (IndexError, TypeError, KeyError): |
| 56 | pass |
| 57 | # Fallback to meta fields if candle data unavailable |
| 58 | meta = chart_result.get("meta", {}) |
| 59 | return meta.get("previousClose") or meta.get("chartPreviousClose") |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.