Back to skill

Security audit

Stock Deep Dive

Security checks for vulnerabilities and agentic risk

Overview

This stock-research skill mostly matches its stated purpose, but its optional X/Twitter integration handles live session tokens too broadly and depends on unpinned external tools.

Review before installing. Use a dedicated environment, do not place unrelated secrets in the skill .env, prefer scoped secret injection for only AUTH_TOKEN and CT0, pin/audit dependencies, and avoid global bird installation unless you trust and can verify that tool. Treat the generated financial analysis as informational only.

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 (2)

T08 · Insecure Dependencies

Warning
Location
scripts/install_deps.py:7
Finding

Unpinned Third-Party Dependencies Permit Mutable Installation-Time Code

Content
View full analysis

Vulnerability Details

File Location: scripts/install_deps.py:7-16; additional dependency declarations appear in stock-analysis/pyproject.toml:7-50 and the optional global npm installation instruction appears in stock-analysis/SKILL.md:151
Vulnerability Type: Unpinned executable dependencies and insufficient supply-chain integrity controls
Risk Level: Medium

Complete Code Snippet

python
def main():
    skill_dir = Path(__file__).parent
    venv_dir = skill_dir / '.venv'
    
    # uv venv
    subprocess.run([sys.executable, '-m', 'uv', 'venv', str(venv_dir)], check=True)
    
    # Activate & pip
    pip = venv_dir / 'bin' / 'pip'
    subprocess.run([pip, 'install', 'yfinance', 'pandas', 'numpy', 'requests', 'jsonschema'], check=True)

The project also declares numerous dependencies without version constraints:

toml
dependencies = [
    "yfinance",
    "pandas",
    "numpy",
    "ta",
    "rich",
    "tabulate",
    "pytz",
    "requests",
    "lxml",
    "beautifulsoup4",
    "feedparser",
    "tenacity",
    "humanize",
    "tqdm",
    "edgartools",
    "fear-and-greed",
    "httpx",
    "python-dateutil",
    "jinja2",
    "markdown-it-py",
    "rank-bm25",
    "rapidfuzz",
    "textdistance",
    "unidecode",
    "curl-cffi",
    "frozendict",
    "filelock",
    "platformdirs",
    "pycparser",
    "soupsieve",
    "sgmllib3k",
    "nest-asyncio",
    "multitasking",
    "pyarrow",
    "orjson",
    "peewee",
    "pydantic",
    "typing-extensions",
    "idna",
    "charset-normalizer",
    "urllib3",
    "certifi",
    "cffi",
    "h11",
    "httpcore",
    "pydantic-core",
    "attrs",
    "cattrs",
    "stamina",
    "markupsafe",
    "mdurl",
    "pygments",
    "pyrate-limiter",
]

Technical Analysis

The installer resolves package names to whatever versi ...[truncated 1881 chars]

Remediation
View remediation

Remediation Suggestions

  1. Pin every direct dependency to a reviewed version and constrain transitive resolution through a committed lockfile.
  2. Generate and verify package hashes, such as with a hash-locked requirements file and --require-hashes.
  3. Use a trusted, explicitly configured package index and disable unintended extra indexes to reduce dependency-confusion exposure.
  4. Audit and remove dependencies that are not directly required.
  5. Run installation inside a dedicated, non-privileged virtual environment.
  6. Pin the documented bird npm package to a reviewed exact version and avoid global installation.
  7. Add automated dependency vulnerability, provenance, and lockfile-drift checks to the release process.

T09 · Insecure Skill Coding Practices

Warning
Location
stock-analysis/scripts/hot_scanner.py:22
Finding

External Twitter CLI Receives the Entire Parent Environment and All Project Environment Variables

Content
View full analysis

Vulnerability Details

File Location: stock-analysis/scripts/hot_scanner.py:22-30,387-391; the same pattern occurs in stock-analysis/scripts/rumor_scanner.py:30-38,79-81,132-134
Vulnerability Type: Excessive disclosure of environment variables to a third-party executable
Risk Level: Medium

Complete Code Snippet

In stock-analysis/scripts/hot_scanner.py:

python
# Load .env file if exists
ENV_FILE = Path(__file__).parent.parent / ".env"
if ENV_FILE.exists():
    with open(ENV_FILE) as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#") and "=" in line:
                key, value = line.split("=", 1)
                os.environ[key] = value
python
env = os.environ.copy()
result = subprocess.run(
    [bird_bin, "search", query, "-n", "15", "--json"],
    capture_output=True, text=True, timeout=30, env=env
)

In stock-analysis/scripts/rumor_scanner.py:

python
BIRD_CLI = "/home/clawdbot/.nvm/versions/node/v24.12.0/bin/bird"
BIRD_ENV = Path(__file__).parent.parent / ".env"

def load_env():
    """Load environment variables from .env file."""
    if BIRD_ENV.exists():
        for line in BIRD_ENV.read_text().splitlines():
            if '=' in line and not line.startswith('#'):
                key, value = line.split('=', 1)
                os.environ[key.strip()] = value.strip().strip('"').strip("'")
python
cmd = [BIRD_CLI, 'search', query, '-n', '10', '--json']
env = os.environ.copy()

result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, env=env)
python
cmd = [BIRD_CLI, 'search', query, '-n', '15', '--json']
env = os.environ.copy()

result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, env=env)

Technical Analysis

The documented X integration requires only AUTH_TOKEN and CT0. Instead of supplying only thos ...[truncated 2338 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse only the explicitly required AUTH_TOKEN and CT0 keys from .env.
  2. Do not copy .env values into global os.environ.
  3. Construct a minimal child environment containing only required authentication values and essential runtime variables such as a controlled PATH, locale, and certificate configuration.
  4. Resolve the bird executable to a configured absolute path and verify that it is the expected reviewed installation.
  5. Reject unknown .env keys or keep unrelated credentials in separate, purpose-specific secret stores.
  6. Require restrictive filesystem permissions on .env, such as user-only read and write access.
  7. Prefer a supported API client with explicit credential parameters over passing secrets through a broad process environment.
  8. Pin and verify the external CLI as described in the dependency remediation.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (84)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · stock-analysis/App-Plan.md (reported line 114)May include surrounding context.

md
GET  /portfolios
   POST /portfolios
   PUT  /portfolios/{id}
   DELETE /portfolios/{id}

   GET  /portfolios/{id}/assets
   POST /portfolios/{id}/assets

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · stock-analysis/App-Plan.md (reported line 119)May include surrounding context.

md
GET  /portfolios/{id}/assets
   POST /portfolios/{id}/assets
   PUT  /portfolios/{id}/assets/{ticker}
   DELETE /portfolios/{id}/assets/{ticker}

   GET  /portfolios/{id}/performance?period=weekly
   GET  /portfolios/{id}/summary

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · stock-analysis/App-Plan.md (reported line 126)May include surrounding context.

md
GET  /alerts
   POST /alerts
   DELETE /alerts/{id}

   GET  /user/subscription
   POST /user/subscription/upgrade

Self-Modification

High
Category
Rogue Agent
Confidence
85% confidence
Finding

Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Content

Scanner excerpt · stock-analysis/TODO.md (reported line 88)May include surrounding context.

md
- [ ] Add timeout per indicator (10s max)
- [ ] Test with multiple stocks in sequence
- [ ] Measure actual runtime improvement
- [ ] Update SKILL.md with new runtime (target: 3-4s)

**Expected Impact**:
- Reduce runtime from 6-10s to 3-4s per stock

Self-Modification

High
Category
Rogue Agent
Confidence
85% confidence
Finding

Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Content

Scanner excerpt · stock-analysis/TODO.md (reported line 193)May include surrounding context.

md
- [ ] Add timeout per indicator (10s max)
- [ ] Test with multiple stocks in sequence
- [ ] Measure actual runtime improvement
- [ ] Update SKILL.md with new runtime (target: 3-4s)

**Expected Impact**:
- Reduce runtime from 6-10s to 3-4s per stock

Credential Access

High
Category
Privilege Escalation
Confidence
93% confidence
Finding

The example directs users to place live authentication tokens in a local .env file inside the project directory, which is a common source of accidental disclosure through version control, backups, debugging output, and local file access by other tools. In an automation-oriented skill, this is more dangerous because users may operationalize the setup and propagate secrets into scripts, caches, or shared environments.

Content

Scanner excerpt · stock-analysis/docs/HOT_SCANNER.md (reported line 149)May include surrounding context.

Create .env file in the skill directory:

bash
# /path/to/stock-analysis/.env
AUTH_TOKEN=your_auth_token_here
CT0=your_ct0_token_here

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

Accessing a repository-local .env file is not inherently malicious, but in this skill it increases security risk because secrets are later made available to an external subprocess. The credential access is broader than necessary for simple trend scanning and lacks clear scoping or disclosure.

Content

Scanner excerpt · stock-analysis/scripts/hot_scanner.py (reported line 22)May include surrounding context.

python
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed

# Load .env file if exists
ENV_FILE = Path(__file__).parent.parent / ".env"
if ENV_FILE.exists():
    with open(ENV_FILE) as f:

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

The .env existence check is part of a broader pattern of secret harvesting from local configuration. In context, this matters because the script later invokes a third-party CLI with inherited environment variables, creating a plausible exfiltration path.

Content

Scanner excerpt · stock-analysis/scripts/hot_scanner.py (reported line 23)May include surrounding context.

python
from concurrent.futures import ThreadPoolExecutor, as_completed

# Load .env file if exists
ENV_FILE = Path(__file__).parent.parent / ".env"
if ENV_FILE.exists():
    with open(ENV_FILE) as f:
        for line in f:

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
99% confidence
Finding

os.environ.copy() captures the entire environment, including any secrets loaded from .env or inherited from the host, and passes them to an external CLI. This unnecessarily expands the trust boundary and can leak credentials, API keys, tokens, or internal configuration to another executable.

Content

Scanner excerpt · stock-analysis/scripts/hot_scanner.py (reported line 387)May include surrounding context.

python
for category, query in searches:
                try:
                    env = os.environ.copy()
                    result = subprocess.run(
                        [bird_bin, "search", query, "-n", "15", "--json"],
                        capture_output=True, text=True, timeout=30, env=env

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · stock-analysis/scripts/rumor_scanner.py (reported line 30)May include surrounding context.

python
# Bird CLI path
BIRD_CLI = "/home/clawdbot/.nvm/versions/node/v24.12.0/bin/bird"
BIRD_ENV = Path(__file__).parent.parent / ".env"

def load_env():
    """Load environment variables from .env file."""

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · stock-analysis/scripts/rumor_scanner.py (reported line 33)May include surrounding context.

python
BIRD_ENV = Path(__file__).parent.parent / ".env"

def load_env():
    """Load environment variables from .env file."""
    if BIRD_ENV.exists():
        for line in BIRD_ENV.read_text().splitlines():
            if '=' in line and not line.startswith('#'):

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
97% confidence
Finding

Copying the full parent environment and passing it to an external CLI can leak unrelated secrets such as API keys, tokens, and cloud credentials to that child process. In a skill context, this is more dangerous because agent runtimes often inject many sensitive variables, and the external tool may perform network operations that could exfiltrate them indirectly.

Content

Scanner excerpt · stock-analysis/scripts/rumor_scanner.py (reported line 79)May include surrounding context.

python
for query in queries[:4]:  # Limit to avoid rate limits
        try:
            cmd = [BIRD_CLI, 'search', query, '-n', '10', '--json']
            env = os.environ.copy()
            
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, env=env)

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
97% confidence
Finding

This second call repeats the same risky pattern of forwarding the entire environment to a network-capable third-party CLI. If the process environment contains credentials unrelated to Twitter/news access, those secrets become accessible to the child process without necessity.

Content

Scanner excerpt · stock-analysis/scripts/rumor_scanner.py (reported line 132)May include surrounding context.

python
for query in queries[:3]:
        try:
            cmd = [BIRD_CLI, 'search', query, '-n', '15', '--json']
            env = os.environ.copy()
            
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, env=env)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/analyze_enrich.py (reported line 6)May include surrounding context.

python
import subprocess, json, sys
ticker = sys.argv[1]
raw = {'mock': '8-dim from stock-analysis'}  # Deprecated, use collect_deep.py
panel = json.loads(subprocess.check_output(['python3', 'investor_panel.py', ticker]))
dcf = subprocess.check_output(['python3', 'fin_models.py', ticker]).decode()
raw['uzi_panel'] = panel
raw['uzi_dcf'] = dcf

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/analyze_enrich.py (reported line 7)May include surrounding context.

python
ticker = sys.argv[1]
raw = {'mock': '8-dim from stock-analysis'}  # Deprecated, use collect_deep.py
panel = json.loads(subprocess.check_output(['python3', 'investor_panel.py', ticker]))
dcf = subprocess.check_output(['python3', 'fin_models.py', ticker]).decode()
raw['uzi_panel'] = panel
raw['uzi_dcf'] = dcf
print(json.dumps(raw))

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/collect.py (reported line 13)May include surrounding context.

python
# analyze_stock --json
try:
    result = subprocess.run(['uv', 'run', '/data/workspace/skills/stock-analysis/scripts/analyze_stock.py', ticker, '--output', 'json'], capture_output=True, text=True, timeout=30)
    raw['analyze_stock'] = json.loads(result.stdout) if result.returncode == 0 else {'error': result.stderr}
except Exception as e:
    raw['analyze_stock'] = {'error': str(e)}

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

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

python
# fundamentals
try:
    result = subprocess.run(['uv', 'run', '/data/workspace/skills/stock-fundamentals/src/main.py', ticker], capture_output=True, text=True, timeout=30)
    raw['fundamentals'] = result.stdout.strip()
except Exception as e:
    raw['fundamentals'] = {'error': str(e)}

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/collect.py (reported line 27)May include surrounding context.

python
# dividend
try:
    result = subprocess.run(['uv', 'run', '/data/workspace/skills/stock-analysis/scripts/dividends.py', ticker], capture_output=True, text=True, timeout=30)
    raw['dividend'] = result.stdout.strip() if 'does not pay' not in result.stdout else None
except Exception as e:
    raw['dividend'] = None

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/collect.py (reported line 34)May include surrounding context.

python
# momentum (reuse analyze --fast for RSI/vol/mom extract)
try:
    result = subprocess.run(['uv', 'run', '/data/workspace/skills/stock-analysis/scripts/analyze_stock.py', ticker, '--fast', '--output', 'json'], capture_output=True, text=True, timeout=30)
    mom = json.loads(result.stdout)['components']['momentum'] if result.returncode == 0 else {}
    raw['momentum'] = mom
except Exception as e:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
72% confidence
Finding

This code executes another script using attacker-influenced input (ticker) and trusts the returned output without validation. Although it avoids shell metacharacter injection by using argument lists, it still expands the attack surface to another executable component whose argument parsing or runtime behavior may be unsafe.

Content

Scanner excerpt · scripts/collect_deep.py (reported line 5)May include surrounding context.

python
# Deep Dive Collector: Parallel 8-dim + UZI
import subprocess, json, sys
ticker = sys.argv[1]
raw_analyze = subprocess.check_output(['/data/workspace/skills/stock-analysis/scripts/analyze_stock.py', ticker]).decode()
raw_div = json.dumps({'ticker': ticker, 'dividend': 'no_dividend'})
raw_fund = subprocess.check_output(['/data/workspace/skills/stock-fundamentals/src/main.py', ticker]).decode()
# Parse RSI/vol from analyze text

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

No manifest is available, so there is no stated purpose that justifies this file's broad orchestration behavior. The code invokes several other local programs, aggregates their outputs, and runs a final gate step, which is a materially broader capability than a narrowly scoped utility script and cannot be validated against any declared intent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This Python file invokes multiple external scripts via subprocess.check_output, including local analyzers and model/gating scripts. Although comments describe some behavior, there is no confirmation prompt, user-facing print/log before execution, or docstring warning that the skill will execute other programs with user-supplied input.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
72% confidence
Finding

The script launches a second external analysis program with the same unvalidated user-controlled ticker and fully trusts its output. This is risky because any weakness in the downstream script becomes reachable through this wrapper, and failures or malicious output can poison the final JSON payload.

Content

Scanner excerpt · scripts/collect_deep.py (reported line 7)May include surrounding context.

python
ticker = sys.argv[1]
raw_analyze = subprocess.check_output(['/data/workspace/skills/stock-analysis/scripts/analyze_stock.py', ticker]).decode()
raw_div = json.dumps({'ticker': ticker, 'dividend': 'no_dividend'})
raw_fund = subprocess.check_output(['/data/workspace/skills/stock-fundamentals/src/main.py', ticker]).decode()
# Parse RSI/vol from analyze text
import re
rsi_match = re.search(r'RSI.*?(\d+\.\d+)', raw_analyze)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
74% confidence
Finding

Executing python3 fin_models.py by relative filename depends on the current working directory and local file contents, making it easier to run an unintended script if the environment is manipulated. The unvalidated ticker is also forwarded into that component, further extending risk into downstream code.

Content

Scanner excerpt · scripts/collect_deep.py (reported line 16)May include surrounding context.

python
vol = vol_match.group(1) if vol_match else 'unknown'
enrich = {'analyze': raw_analyze, 'div': raw_div, 'fund': raw_fund, 'rsi': rsi, 'vol': vol}
panel = {'avg': 5.0, 'votes': ['+10 ROE Buffett', '+0 growth Lynch']}  # Self-contained mock until parse full
dcf = subprocess.check_output(['python3', 'fin_models.py', ticker]).decode()
enrich = {'analyze': raw_analyze, 'div': raw_div, 'fund': raw_fund, 'panel': panel, 'dcf': dcf}
raw = json.dumps(enrich)
print(json.dumps(enrich))

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
76% confidence
Finding

The script invokes a local Python program and passes a large serialized object to it without validating the downstream program's trustworthiness or constraining its behavior. While there is no shell injection here because arguments are passed as a list, this still creates an execution boundary where a compromised or swapped uzi_gate.py could process sensitive data or perform unintended actions.

Content

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

python
enrich = {'analyze': raw_analyze, 'div': raw_div, 'fund': raw_fund, 'panel': panel, 'dcf': dcf}
raw = json.dumps(enrich)
print(json.dumps(enrich))
gate = json.loads(subprocess.check_output(['python3', 'uzi_gate.py'], input=json.dumps(enrich)))
print(json.dumps({'enrich': enrich, 'gate': gate}))