Back to skill

Security audit

Vnstock Free Expert

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a Vietnam stock-analysis workflow, but one helper exposes overly broad local Python invocation and the credential handling can leak API keys into logs or output files.

Review before installing. Use this only in an isolated environment, avoid passing API keys with --api-key, do not feed untrusted arguments to invoke_vnstock.py, and pin/review vnstock dependencies before following the install docs.

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/invoke_vnstock.py:58
Finding
Unrestricted Dynamic Invocation Enables Arbitrary Local Command Execution<![CDATA[ ## Vulnerability Details **File Location**: `scripts/invoke_vnstock.py:58-94` **Vulnerability Type**: Unrestricted dynamic import and method invocation **Risk Level**: Critical ### Vulnerable Code ```python parser.add_argument("--module", default="vnstock", help="Module path containing class, default vnstock") parser.add_argument("--class-name", required=True, help="vnstock exported class name, e.g. Quote") parser.add_argument("--init-kwargs", default="{}", help="JSON object for class initialization kwargs") parser.add_argument("--method", required=True, help="Method name to call") parser.add_argument("--method-args", default="[]", help="JSON array for positional method args") parser.add_argument("--method-kwargs", default="{}", help="JSON object for method kwargs") parser.add_argument("--outdir", default="./outputs", help="Output directory") parser.add_argument("--min-interval-sec", type=float, default=3.2, help="Optional pacing for free-tier safety") parser.add_argument("--api-key", default="", help="Optional VNStock API key override") args = parser.parse_args() init_kwargs = parse_json_arg(args.init_kwargs, "init-kwargs") method_args = parse_json_list_arg(args.method_args, "method-args") method_kwargs = parse_json_arg(args.method_kwargs, "method-kwargs") configure_vnstock_api_key(args.api_key or None) mod = importlib.import_module(args.module) if not hasattr(mod, args.class_name): raise AttributeError(f"Class not found in module {args.module}: {args.class_name}") cls = getattr(mod, args.class_name) client = cls(**init_kwargs) if not hasattr(client, args.method): raise AttributeError(f"Method not found on {args.class_name}: {args.method}") method = getattr(client, args.method) limiter = RateLimiter(min_interval_sec=args.min_interval_sec) limiter.wait() result = method(*method_args, **method_kwargs) ``` ### Technical Analysis The command-line caller controls the imported module, selected class, constructor arguments, method name, ...[truncated 1887 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--module` option and import only the expected `vnstock` package. 2. Define explicit allowlists of supported VNStock classes and methods, for example: - `Quote`: `history` - `Listing`: approved listing methods - `Company`: approved company-information methods - `Finance`: approved financial-statement methods 3. Reject private members, inherited utility methods, and all methods absent from the allowlist. 4. Validate constructor and method arguments against per-method schemas rather than accepting arbitrary JSON objects and arrays. 5. Reject callable objects that do not originate from approved `vnstock` modules. 6. Run provider operations in a sandboxed subprocess with restricted filesystem access, network destinations, environment variables, and resource limits. 7. Add negative tests confirming that modules such as `subprocess`, `os`, `pathlib`, and `builtins` cannot be imported or invoked. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run_pipeline.py:9
Finding
API Keys May Be Exposed Through Process Arguments, Logs, and Invocation Artifacts<![CDATA[ ## Vulnerability Details **File Locations**: `scripts/run_pipeline.py:9-12, 23-38, 40-58`; `scripts/invoke_vnstock.py:58-66, 98-116`; `SKILL.md:45-48` **Vulnerability Type**: Plaintext credential exposure **Risk Level**: High ### Vulnerable Code The pipeline accepts a secret through a command-line argument, prints complete child commands, and forwards the secret to those child processes: ```python def run(cmd): print("Running:", " ".join(cmd)) subprocess.run(cmd, check=True) ``` ```python parser.add_argument("--api-key", default="", help="Optional VNStock API key override") ``` ```python run([ sys.executable, str(base / "build_universe.py"), "--source", args.source, "--mode", args.mode, "--group", args.group, "--exchange", args.exchange, "--symbols", args.symbols, "--outdir", args.outdir, "--api-key", args.api_key, ]) ``` The generic invoker accepts arbitrary initialization and method arguments: ```python parser.add_argument("--init-kwargs", default="{}", help="JSON object for class initialization kwargs") parser.add_argument("--method-args", default="[]", help="JSON array for positional method args") parser.add_argument("--method-kwargs", default="{}", help="JSON object for method kwargs") parser.add_argument("--api-key", default="", help="Optional VNStock API key override") ``` Those values are then stored verbatim in generated JSON and Markdown files: ```python payload = { "generated_at": ts, "module": args.module, "class_name": args.class_name, "init_kwargs": init_kwargs, "method": args.method, "method_args": method_args, "method_kwargs": method_kwargs, "result": serial, } write_json(json_path, payload) lines = [ f"# VNStock Invocation Result ({ts})", "", f"- Class: `{args.class_name}`", f"- Method: `{args.method}`", f"- Module: `{args.module}`", f"- Init kwargs: `{json.dumps(init_kwargs, ensure_ascii=False)}`", f"- Method args: `{jso ...[truncated 2367 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--api-key` command-line option from all scripts. 2. Load credentials from a protected environment variable, operating-system secret store, or file descriptor that is not visible in process arguments. 3. Pass credentials to child processes through a minimally scoped environment dictionary rather than command-line arguments. 4. Redact argument names matching patterns such as `api_key`, `token`, `secret`, `password`, `authorization`, and `credential` before logging or serialization. 5. Do not include constructor or method arguments in reports unless they have passed an explicit safe-field allowlist. 6. Create output files with restrictive permissions, such as owner read/write only. 7. Ensure `.env`, output artifacts, and logs are excluded from source control. 8. Add automated tests that inject sentinel secrets and verify that they do not appear in process logs, JSON outputs, Markdown outputs, or error messages. 9. Rotate any credentials that may already have been used through these command-line or artifact-writing paths. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/vnstock/11-best-practices.md:43
Finding
Documentation Recommends Unsafe Pickle Deserialization from Predictable Cache Files<![CDATA[ ## Vulnerability Details **File Locations**: `references/vnstock/11-best-practices.md:43-69`; `references/vnstock/10-connector-guide.md:356-373` **Vulnerability Type**: Unsafe deserialization **Risk Level**: High ### Vulnerable Code ```python def get(self, source, symbol, start, end): from vnstock import Quote cache_path = self._get_cache_path(source, symbol, start, end) # Từ cache nếu còn hợp lệ if self._is_cache_valid(cache_path): with open(cache_path, 'rb') as f: return pickle.load(f) # Fetch từ API quote = Quote(source=source, symbol=symbol) df = quote.history(start=start, end=end) # Lưu cache with open(cache_path, 'wb') as f: pickle.dump(df, f) return df ``` The connector guide contains the same unsafe loading pattern: ```python if os.path.exists(cache_file): file_age = datetime.datetime.now().timestamp() - os.path.getmtime(cache_file) if file_age < CACHE_TTL: with open(cache_file, 'rb') as f: return pickle.load(f) quote = Quote(source=source, symbol=symbol) df = quote.history(start=start, end=end, resolution="1D") os.makedirs(CACHE_DIR, exist_ok=True) with open(cache_file, 'wb') as f: pickle.dump(df, f) ``` ### Technical Analysis Python pickle is not a data-only format. During `pickle.load()`, serialized objects may invoke attacker-selected reconstruction functions. Loading a malicious pickle can therefore execute arbitrary Python code before a legitimate DataFrame is returned. The examples use predictable, relative cache directories and construct filenames from values such as source, symbol, and dates. They do not verify file ownership, permissions, integrity, or containment within the intended cache directory. A local attacker who can create or replace a cache file can cause code execution on the next cache hit. Unsanitized filename components can also create path-traversal risk if these examples are adapted to ...[truncated 1152 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace pickle caches with non-executable formats such as Parquet, JSON, or CSV. 2. Define and validate the expected schema after loading cached data. 3. Sanitize source, symbol, and date components using strict allowlists. 4. Resolve each generated path and verify that it remains inside the intended cache directory. 5. Create cache directories and files with owner-only permissions where possible. 6. If a binary format is required, use one that does not support arbitrary object reconstruction. 7. If pickle cannot be removed, cryptographically authenticate each cache file with a key stored separately and reject files with missing or invalid authentication. This reduces tampering risk but does not make untrusted pickle safe. 8. Update every reference example containing `pickle.load()` so Agents do not reproduce the unsafe pattern. ]]>

T08 · Insecure Dependencies

Warning
Location
references/vnstock/02-installation.md:11
Finding
Installation Guidance Uses Unpinned Packages and a Mutable Git Repository Head<![CDATA[ ## Vulnerability Details **File Location**: `references/vnstock/02-installation.md:11-27` **Vulnerability Type**: Unpinned and mutable third-party dependency installation **Risk Level**: Medium ### Vulnerable Code ```bash # Option 1: Install from PyPI (Stable) pip install vnstock ``` ```bash # Option 2: Install from GitHub (Latest Development) pip install git+https://github.com/vnstock-lab/vnstock.git ``` ```bash # Option 3: Install from Local (Dev Version) # Clone or copy private_packages directory pip install git+https://github.com/vnstock-lab/vnstock.git ``` The same guide also recommends installing a broad set of dependencies without exact versions or hashes: ```bash pip install pandas requests beautifulsoup4 lxml pydantic tenacity \ python-dateutil aiohttp tqdm packaging python-dotenv ``` ### Technical Analysis The package installation commands do not pin reviewed versions or verify package hashes. The Git-based command installs directly from the current default branch, so the executed source can change after this Skill has been audited. Python package installation may execute build backend and installation code, making dependency source integrity security-sensitive. The mutable Git recommendation is more risky than installing a reviewed release. The broad manual installation command also expands the dependency surface and allows future, potentially incompatible versions to be selected. No evidence was found that the named repository or packages are currently malicious. The vulnerability is the non-reproducible and insufficiently verified installation process. ### Attack Path 1. A user follows the installation instructions at a later date. 2. The package release, dependency resolution result, or Git repository head differs from the version reviewed during the Skill audit. 3. A compromised maintainer account, repository, release process, or transitive dependency introduces malicious installation or runtime code. 4. `pip` download ...[truncated 613 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin VNStock to a reviewed release version. 2. If Git installation is necessary, pin an immutable commit hash rather than a branch or repository head. 3. Maintain a lockfile containing exact transitive dependency versions. 4. Use hash-verified installations, such as a requirements file with `--require-hashes`. 5. Prefer the verified package-index release over a development-branch installation. 6. Remove the duplicate “local” option that actually installs from the same remote Git repository. 7. Avoid manually installing a broad dependency list unless required; rely on reviewed package metadata and a locked environment. 8. Perform installation in an isolated virtual environment without administrator privileges. 9. Add a documented update and review process before changing pinned dependency versions. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (55)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
If the implemented behavior is only ranking/scoring from preexisting CSVs while the description promises vnstock API usage and free-tier safety enforcement, the mismatch is still a true vulnerability because it conceals the real operational boundaries. Misleading descriptions reduce the effectiveness of human and automated review and can mask stale-data workflows or unvalidated downstream assumptions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If the implemented behavior is only ranking/scoring from preexisting CSVs while the description promises vnstock API usage and free-tier safety enforcement, the mismatch is still a true vulnerability because it conceals the real operational boundaries. Misleading descriptions reduce the effectiveness of human and automated review and can mask stale-data workflows or unvalidated downstream assumptions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
If the implemented behavior is only ranking/scoring from preexisting CSVs while the description promises vnstock API usage and free-tier safety enforcement, the mismatch is still a true vulnerability because it conceals the real operational boundaries. Misleading descriptions reduce the effectiveness of human and automated review and can mask stale-data workflows or unvalidated downstream assumptions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
If the implemented behavior is only ranking/scoring from preexisting CSVs while the description promises vnstock API usage and free-tier safety enforcement, the mismatch is still a true vulnerability because it conceals the real operational boundaries. Misleading descriptions reduce the effectiveness of human and automated review and can mask stale-data workflows or unvalidated downstream assumptions.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
If the implemented behavior is only ranking/scoring from preexisting CSVs while the description promises vnstock API usage and free-tier safety enforcement, the mismatch is still a true vulnerability because it conceals the real operational boundaries. Misleading descriptions reduce the effectiveness of human and automated review and can mask stale-data workflows or unvalidated downstream assumptions.

Hidden Instructions

High
Category
Prompt Injection
Content
**Trạng thái:** Chưa triển khai đầy đủ

- **Layer 5 (Analytics)**: Chỉ số kỹ thuật, mô hình định giá, vv (chưa đầy đủ) - có thư viện vnstock_ta cung cấp tính toán bộ chỉ báo kỹ thuật.​
- **Layer 6 (Macro)**: Chỉ số kinh tế, hàng hóa - chỉ có trong thư viện vnstock_data yêu cầu tham gia gói tài trợ Vnstock.
- **Layer 7 (Insights)**: Screener, rankings top stocks, vv - (Chưa đầy đủ) - chỉ có trong thư viện vnstock_data yêu cầu tham gia gói tài trợ Vnstock.
Confidence
60% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

YARA rule 'agent_skill_remote_bootstrap_execution': Remote script or code download followed by execution/bootstrap installation [agent_skills]

High
Category
YARA Match
Content
# 02 - Cài Đặt & Cấu Hình

## 📦 Yêu Cầu Hệ Thống

- **Python**: 3.8 hoặc cao hơn (khuyến nghị 3.10+)
- **OS**: Windows, macOS, hoặc Linux
- **Internet**: Kết nối internet ổn định

## 🚀 Cài Đặt Nhanh

### Option 1: Cài từ PyPI (Stable)

```bash
pip install vnstock
```

### Option 2: Cài từ GitHub (Latest Development)

```bash
pip install git+https://github.com/vnstock-lab/vnstock.git
```

### Option 3: Cài từ Local (Dev Version)

```bash
# Clone hoặc copy thư mục private_packages
pip install git+https://github.com/vnstock-lab/vnstock.git
```

## 📋 Dependencies

VNStock phụ thuộc vào các package sau:

```
pandas>=1.3.0          # Xử lý DataFrame
requests>=2.25.0       # HTTP requests
beautifulsoup4>=4.9.0  # Web scraping
lxml>=4.6.0            # XML parsing
pydantic>=1.8.0        # Data validation
tenacity>=8.0.0        # Retry logic
python-dateutil>=2.8.0 # Date utilities
aiohttp>=3.7.0
Confidence
89% confidence
Finding
The documentation recommends installing directly from a GitHub repository using 'pip install git+https://...', which executes setup/build logic from a remote source outside a pinned, reviewed package release. In an agent skill context, remote bootstrap instructions are more dangerous because users may follow them automatically, increasing supply-chain risk if the repository, dependency chain, or branch state is compromised.

Credential Access

High
Category
Privilege Escalation
Content
Nếu sử dụng external APIs như FMP, XNO, DNSE:

```bash
# .env file
FMP_API_KEY=your_fmp_api_key_here
XNO_API_KEY=your_xno_api_key_here
DNSE_API_KEY=your_dnse_api_key_here
Confidence
90% confidence
Finding
The snippet shows external API keys stored in a .env file, which is a legitimate pattern, but the documentation does not sufficiently warn that these values are credentials requiring protection. In a skill used by agents and users, this can lead to secret leakage through accidental commits, logs, prompt sharing, or unsafe local storage.

Credential Access

High
Category
Privilege Escalation
Content
FMP_API_KEY = os.getenv('FMP_API_KEY')

if not FMP_API_KEY:
    print("❌ Please set FMP_API_KEY in .env")
else:
    print("✅ FMP_API_KEY configured")
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
FMP_API_KEY = os.getenv('FMP_API_KEY')

if not FMP_API_KEY:
    print("❌ Please set FMP_API_KEY in .env")
else:
    print("✅ FMP_API_KEY configured")
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
FMP_API_KEY = os.getenv('FMP_API_KEY')

if not FMP_API_KEY:
    print("❌ Please set FMP_API_KEY in .env")
else:
    print("✅ FMP_API_KEY configured")
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
FMP_API_KEY = os.getenv('FMP_API_KEY')

if not FMP_API_KEY:
    print("❌ Please set FMP_API_KEY in .env")
else:
    print("✅ FMP_API_KEY configured")
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if env_key:
        return env_key.strip()

    # Skill-local fallback: <skill_root>/.env
    # scripts/common.py -> skill root is parents[1]
    env_path = Path(__file__).resolve().parents[1] / ".env"
    values = _read_env_file(env_path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if env_key:
        return env_key.strip()

    # Skill-local fallback: <skill_root>/.env
    # scripts/common.py -> skill root is parents[1]
    env_path = Path(__file__).resolve().parents[1] / ".env"
    values = _read_env_file(env_path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if env_key:
        return env_key.strip()

    # Skill-local fallback: <skill_root>/.env
    # scripts/common.py -> skill root is parents[1]
    env_path = Path(__file__).resolve().parents[1] / ".env"
    values = _read_env_file(env_path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if env_key:
        return env_key.strip()

    # Skill-local fallback: <skill_root>/.env
    # scripts/common.py -> skill root is parents[1]
    env_path = Path(__file__).resolve().parents[1] / ".env"
    values = _read_env_file(env_path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if env_key:
        return env_key.strip()

    # Skill-local fallback: <skill_root>/.env
    # scripts/common.py -> skill root is parents[1]
    env_path = Path(__file__).resolve().parents[1] / ".env"
    values = _read_env_file(env_path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if env_key:
        return env_key.strip()

    # Skill-local fallback: <skill_root>/.env
    # scripts/common.py -> skill root is parents[1]
    env_path = Path(__file__).resolve().parents[1] / ".env"
    values = _read_env_file(env_path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if env_key:
        return env_key.strip()

    # Skill-local fallback: <skill_root>/.env
    # scripts/common.py -> skill root is parents[1]
    env_path = Path(__file__).resolve().parents[1] / ".env"
    values = _read_env_file(env_path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
if env_key:
        return env_key.strip()

    # Skill-local fallback: <skill_root>/.env
    # scripts/common.py -> skill root is parents[1]
    env_path = Path(__file__).resolve().parents[1] / ".env"
    values = _read_env_file(env_path)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The implementation contradicts the skill description by exposing arbitrary module/class/method invocation and serializing whatever result is returned. That mismatch is dangerous because operators may trust the skill as a narrowly scoped, free-tier-safe stock workflow while it actually behaves as a general-purpose reflection wrapper with file output.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
Allowing an arbitrary module path to be imported is unjustified for a Vietnam stock analysis skill and greatly broadens the attack surface. It enables the script to load non-VNStock modules and combine them with reflective class/method access, effectively escaping the intended domain constraints.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill advertises executable scripts, shell commands, filesystem outputs, optional environment-variable loading from `.env`, and internet-backed API usage, but it declares no explicit tool scope or permissions boundaries. In an agent environment, that mismatch can allow broader-than-expected shell, file, and secret access, increasing the risk of unsafe execution paths or unintended exposure of local data and API keys.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The API key example shows direct key configuration without any warning about secure secret handling, storage, rotation, or redaction. In practice, users may hardcode credentials in code, logs, notebooks, or skill configs, increasing the risk of credential leakage and unauthorized third-party API usage.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The document describes web scraping and third-party API access, but does not clearly warn that using the skill will initiate outbound network requests to external services. In an agent skill context, this can cause users or downstream systems to unknowingly transmit metadata, prompts, symbols, or credentials to third parties, creating privacy, compliance, and trust risks.

Static analysis

No suspicious patterns detected.