Back to skill

Security audit

Electricity Forecasting Framework

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent electricity forecasting package, but its deployment guidance and scripts include unsafe production patterns that deserve careful review before use.

Install only if you are comfortable reviewing and hardening the deployment pieces. Do not load untrusted .joblib or PyTorch model files, avoid generated source from untrusted feature configs, run scheduled jobs under a dedicated non-root account, add authentication and request limits before exposing the API, and pin dependencies for production builds.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/deploy_model.py:20
Finding
Unsafe Deserialization of Untrusted Model Artifacts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/deploy_model.py:20-23` **Additional Locations**: `scripts/deploy_model.py:82-83`, `references/deployment.md:47-49`, `references/deployment.md:91-93` **Vulnerability Type**: Unsafe pickle-compatible model deserialization **Risk Level**: High ### Vulnerable Code ```python def load_model(model_path): """Load trained model.""" print(f"Loading model from {model_path}") package = joblib.load(model_path) ``` The generated inference wrapper repeats the unsafe operation: ```python def __init__(self, model_path='model.joblib'): self.model = joblib.load(model_path) ``` The deployment guide also recommends unsafe model loading: ```python def load_model_for_deployment(model_path): """Load complete model package.""" package = joblib.load(model_path) return package['model'], package['feature_engineer'], package['metadata'] ``` ```python def load_pytorch_model(model_class, checkpoint_path): """Load PyTorch model for inference.""" checkpoint = torch.load(checkpoint_path, map_location='cpu') ``` ### Technical Analysis `joblib.load()` uses pickle-compatible deserialization. Pickle formats can encode calls to arbitrary Python functions through mechanisms such as `__reduce__`. Consequently, loading a maliciously constructed model artifact can execute operating-system commands before the returned object is inspected. The deployment CLI accepts a caller-provided path through `--model`, and the generated API automatically deserializes `model.joblib` during module initialization. No signature, cryptographic digest, trusted-directory restriction, ownership check, or safe serialization format is enforced. The documented `torch.load()` call presents a similar risk when loading legacy or attacker-controlled checkpoints because it does not explicitly restrict loading to tensor weights through an appropriate safe-loading mode. ### Attack Path 1. An attacker creates a maliciou ...[truncated 1084 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not deserialize model artifacts from untrusted or user-writable locations. 2. Prefer non-executable formats such as ONNX or framework-specific safe tensor formats. 3. Digitally sign model artifacts and verify their signatures before loading. 4. Maintain an allowlist of trusted model directories and reject symlinks, unexpected owners, or group/world-writable files. 5. If pickle-compatible loading remains unavoidable, isolate it in a disposable, least-privileged sandbox without secrets, network access, or sensitive mounts. 6. For PyTorch state dictionaries, use a supported safe-loading mode such as `torch.load(..., weights_only=True)` and construct the model architecture from validated local code. 7. Perform integrity verification before deserialization; validation after `joblib.load()` is too late. 8. Run the API and deployment process as a dedicated unprivileged account. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/deploy_model.py:85
Finding
Python Code Injection in Generated Inference Wrapper<![CDATA[ ## Vulnerability Details **File Location**: `scripts/deploy_model.py:85-88` **Input Locations**: `scripts/deploy_model.py:473-477`, `scripts/deploy_model.py:489-491` **Vulnerability Type**: Unsafely generated Python source code **Risk Level**: High ### Vulnerable Code ```python def __init__(self, model_path='model.joblib'): self.model = joblib.load(model_path) self.model_type = '{model_type}' self.feature_columns = {json.dumps(feature_config.get('feature_columns', []))} self.lookback = {feature_config.get('lookback', 168)} self.horizon = {feature_config.get('horizon', 24)} ``` The interpolated values can originate from unrestricted command-line and JSON inputs: ```python parser.add_argument('--model', required=True, help='Trained model file (.joblib)') parser.add_argument('--output', required=True, help='Output deployment directory') parser.add_argument('--model-type', default='lightgbm', help='Model type') parser.add_argument('--feature-config', help='Feature config JSON file') ``` ```python if args.feature_config: with open(args.feature_config, 'r') as f: feature_config = json.load(f) ``` ### Technical Analysis The deployment script constructs executable Python source through an f-string. `model_type` is embedded between single quotes without escaping, while `lookback` and `horizon` are inserted directly as Python expressions without integer validation. A malicious `model_type` can terminate the generated string and append Python statements. Likewise, a crafted JSON configuration can place a string containing Python syntax into `lookback` or `horizon`. The resulting payload is written to `forecaster.py` and executes when that module is run or imported by `api_server.py`. Using `json.dumps()` for `feature_columns` is safer than raw interpolation, but it does not mitigate the other unvalidated values. ### Attack Path 1. An attacker controls or influences `--model-type` or the JSON file passed through `--feat ...[truncated 882 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `model_type` against a fixed allowlist such as `lightgbm`, `xgboost`, and `random_forest`. 2. Require `lookback` and `horizon` to be integers within documented bounds before generating any files. 3. Avoid generating Python source from external configuration. Store deployment settings in a JSON file and parse them at runtime. 4. If source generation is unavoidable, serialize validated values with safe Python literal encoding and never interpolate raw expressions. 5. Reject unknown configuration keys and unexpected data types through a strict schema, such as a Pydantic model. 6. Add tests using quotes, newlines, semicolons, comments, and expression-like strings to verify that configuration cannot alter generated syntax. 7. Treat model-embedded configuration as untrusted until it has passed the same schema validation. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/deploy_model.py:235
Finding
Unpinned Dependencies Produce Mutable and Non-Reproducible Deployment Builds<![CDATA[ ## Vulnerability Details **File Location**: `scripts/deploy_model.py:235-247` **Additional Locations**: `scripts/deploy_model.py:344-359`, `references/deployment.md:666-674` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Medium ### Vulnerable Code ```dockerfile FROM python:3.10-slim WORKDIR /app # Install dependencies RUN pip install --no-cache-dir \ pandas \ numpy \ scikit-learn \ lightgbm \ joblib \ fastapi \ uvicorn \ pydantic ``` The generated requirements only specify minimum versions: ```text pandas>=2.0.0 numpy>=1.24.0 scikit-learn>=1.3.0 lightgbm>=4.0.0 joblib>=1.3.0 fastapi>=0.100.0 uvicorn>=0.23.0 pydantic>=2.0.0 ``` ### Technical Analysis The generated Dockerfile installs the latest package versions available at build time. The generated `requirements.txt` uses lower bounds rather than exact reviewed versions and does not include package hashes. The base image is also referenced by a mutable tag instead of an immutable digest. As a result, two builds from identical source can install materially different code. A compromised future release, dependency account, package index, or transitive dependency can introduce malicious code or a newly disclosed vulnerability without any project source change. ### Attack Path 1. A listed package or one of its transitive dependencies publishes a compromised release, or the package distribution channel is compromised. 2. A user rebuilds the generated deployment image. 3. `pip install` resolves the mutable requirement to the compromised version. 4. Malicious code executes during installation, import, API startup, or model inference. 5. The compromised dependency inherits access to the service environment, model files, network, and mounted volumes. ### Impact Assessment The direct privilege level is that of the image build process or running API container. A compromised dependency can alter forecasts, steal environment credent ...[truncated 182 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct and transitive dependency to an exact reviewed version. 2. Generate and commit a lock file using a controlled dependency resolution process. 3. Require verified hashes during installation, for example through `pip --require-hashes`. 4. Pin the Python base image by immutable digest. 5. Build through a trusted internal package mirror containing approved artifacts. 6. Scan dependencies and container images for known vulnerabilities during CI. 7. Generate a software bill of materials and retain dependency provenance. 8. Apply controlled update procedures rather than resolving arbitrary future versions during production builds. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
references/deployment.md:646
Finding
Forecasting Jobs Are Scheduled for Persistent Execution as Root<![CDATA[ ## Vulnerability Details **File Location**: `references/deployment.md:646-654` **Vulnerability Type**: Excessive privileges for scheduled tasks **Risk Level**: High ### Vulnerable Code ```cron # /etc/cron.d/electricity_forecast # Daily forecast at 5 AM 0 5 * * * root /usr/bin/python3 /opt/electricity_forecast/batch_forecast.py >> /var/log/forecast.log 2>&1 # Hourly forecast update 0 * * * * root /usr/bin/python3 /opt/electricity_forecast/hourly_update.py >> /var/log/forecast.log 2>&1 # Daily metrics calculation at 6 AM 0 6 * * * root /usr/bin/python3 /opt/electricity_forecast/calculate_metrics.py >> /var/log/forecast.log 2>&1 # Weekly drift check on Monday at 7 AM 0 7 * * 1 root /usr/bin/python3 /opt/electricity_forecast/check_drift.py >> /var/log/forecast.log 2>&1 # Monthly retraining check on 1st at 8 AM 0 8 1 * * root /usr/bin/python3 /opt/electricity_forecast/check_retrain.py >> /var/log/forecast.log 2>&1 ``` ### Technical Analysis The deployment guide instructs operators to execute all forecasting, monitoring, and retraining jobs as `root`. These operations do not inherently require unrestricted host privileges. Python resolves imported modules from multiple paths, and the jobs are expected to read model and configuration artifacts. If an attacker can modify a scheduled script, imported module, model artifact, configuration file, working directory, or dependency, cron converts that limited write capability into recurring root-level code execution. The scheduled nature of the configuration also makes the execution cross-session and persistent once installed. ### Attack Path 1. An operator installs the documented cron configuration. 2. An attacker gains write access to a scheduled Python script, an imported module, a model file subject to unsafe deserialization, or another file used by the scheduled process. 3. The attacker inserts a payload into that writable execution path. 4. Cron invokes the affected task as `root`. 5. The payload e ...[truncated 483 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a dedicated non-login service account for forecasting jobs. 2. Run each cron task under that account rather than `root`. 3. Ensure code, dependency, and configuration directories are owned by a trusted administrative account and are not writable by the service user. 4. Store mutable forecast outputs and logs in separate narrowly writable directories. 5. Use absolute paths and a fixed, minimal environment and Python module search path. 6. Verify model signatures before loading artifacts. 7. Prefer a sandboxed system service or least-privileged container with filesystem, network, and capability restrictions. 8. Protect cron definitions with root-only write permissions and monitor them for unauthorized changes. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/deploy_model.py:287
Finding
Generated Forecast API Is Exposed Without Authentication or Resource Limits<![CDATA[ ## Vulnerability Details **File Location**: `scripts/deploy_model.py:287-331` **Additional Location**: `scripts/deploy_model.py:503-513` **Vulnerability Type**: Missing API access control, input bounds, and secure error handling **Risk Level**: Medium ### Vulnerable Code ```python class ForecastRequest(BaseModel): historical_load: List[float] temperature: Optional[float] = None humidity: Optional[float] = None timestamp: Optional[str] = None class ForecastResponse(BaseModel): timestamp: str predictions: List[float] forecast_times: List[str] model_type: str horizon: int @app.get("/health") async def health_check(): return {"status": "healthy", "timestamp": datetime.now().isoformat()} @app.post("/forecast", response_model=ForecastResponse) async def get_forecast(request: ForecastRequest): """Generate electricity load forecast.""" try: weather = None if request.temperature is not None: weather = { 'temperature': request.temperature, 'humidity': request.humidity } timestamp = None if request.timestamp: timestamp = datetime.fromisoformat(request.timestamp) result = forecaster.predict( request.historical_load, weather=weather, timestamp=timestamp ) return ForecastResponse(**result) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) ``` The generated startup guidance binds the service to all interfaces: ```python print(" uvicorn api_server:app --host 0.0.0.0 --port 8000") ``` ### Technical Analysis The generated `/forecast` endpoint has no authentication or authorization checks. The documented startup command binds the API to `0.0.0.0`, potentially exposing it to every reachable network. `historical_load` is an unrestricted list of floating-point values. No maximum list le ...[truncated 1458 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require authenticated callers through an API gateway, signed tokens, mutual TLS, or another appropriate mechanism. 2. Enforce authorization based on the intended users and deployment environment. 3. Bind to a private interface by default and expose the service only through a TLS-terminating reverse proxy. 4. Define strict Pydantic bounds for `historical_load`, including exact or maximum length and finite numeric values. 5. Bound temperature, humidity, timestamp length, forecast horizon, and all derived allocations. 6. Configure maximum request-body size, rate limits, concurrency limits, and inference timeouts. 7. Return generic client-facing error messages while logging detailed exceptions to protected server logs. 8. Add monitoring and alerting for request floods, repeated failures, and unusual payload sizes. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The supplied code is clearly related to electricity/load forecasting evaluation, so it is not unrelated or malicious. However, the declared description presents a broad, comprehensive forecasting framework with statistical, ML, and deep learning support plus deployment and uncertainty features. This code chunk actually implements a narrower capability: backtesting/evaluation for three tree-based ML models only (LightGBM, XGBoost, Random Forest). It does not show ARIMA/SARIMA, neural models, uncertainty quantification, or deployment behavior. Because the description materially overstates the capabilities represented by this chunk, the description does not accurately represent what this specific supplied code actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The description claims a comprehensive electricity load and demand forecasting framework with multiple model families, evaluation, and uncertainty quantification. The supplied code chunk only performs preprocessing: loading/validating time series data, imputing missing values, engineering features, optionally merging weather data, adding holiday features, and saving processed outputs. Weather integration is present and relevant, but the core declared capabilities around forecasting methods, model training/inference, evaluation, and deployment are absent from this code. This is therefore a material description-behavior mismatch, with the actual code representing a preprocessing component rather than the full declared framework.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The code is clearly aligned with the general domain of electricity load forecasting, so the primary purpose matches at a high level. However, the declared description materially overstates the implemented capabilities in this code chunk. The script only trains and evaluates a subset of models and saves artifacts locally. It does not implement several specifically claimed model families (ARIMA, GRU, Transformer, TFT), nor uncertainty quantification. It provides generic feature usage and metric calculation, but not explicit weather integration or deployment/production pipeline features. Therefore this is a description-behavior mismatch due to significant missing claimed capabilities, even though the overall forecasting purpose is consistent.

Ae1

High
Category
analysis-evasion
Content
python scripts/train_model.py --model xgboost --data processed/ --horizon 24
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/train_model.py --model xgboost --data processed/ --horizon 24
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
python scripts/train_model.py --model xgboost --data processed/ --horizon 24
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

External Transmission

Medium
Category
Data Exfiltration
Content
# API access example
import requests

url = "https://api.pjm.com/api/v1/inst_load"
params = {
    'startDate': '20240101',
    'endDate': '20240131',
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
**Description**: UK electricity system operator data.

**Source**: https://data.nationalgrideso.com/

**Details**:
- Great Britain grid
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
**Description**: UK electricity system operator data.

**Source**: https://data.nationalgrideso.com/

**Details**:
- Great Britain grid
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
```python
import requests

url = "https://api.openweathermap.org/data/2.5/onecall/timemachine"
params = {
    'lat': 39.9042,
    'lon': 116.4074,
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The holiday feature example defaults to `country='CN'` and adds China-specific logic such as Chinese New Year and northern China heating season. In a general-purpose feature engineering guide, this forces a specific locale and regional assumptions rather than offering a user choice or clearly restricting the guide to China-focused forecasting.

Insecure deserialization: joblib.load()

Medium
Category
Dangerous Code Execution
Content
"""Load trained model."""
    print(f"Loading model from {model_path}")
    
    package = joblib.load(model_path)
    
    if isinstance(package, dict):
        model = package.get('model', package)
Confidence
98% confidence
Finding
The script deserializes an attacker-controlled or untrusted model file using joblib.load(), which relies on Python pickle semantics and can execute arbitrary code during loading. In a deployment utility that accepts a model path from the command line, this creates a direct code execution path if a malicious model artifact is supplied from a compromised registry, shared filesystem, or download source.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The manifest describes an electricity forecasting framework for building and evaluating forecasting systems, but this script also generates a FastAPI server with public endpoints and later packages it into a deployable service. While deployment pipelines are mentioned in the manifest, exposing an HTTP API is a broader operational capability than core forecasting logic and is not reflected in the file's own stated purpose of merely exporting a model for production use.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding
Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Intent-Code Divergence

Low
Confidence
97% confidence
Finding
The top-level documentation frames the script narrowly as a model export utility. In practice, the implementation writes multiple deployment artifacts including a web API server, container definition, dependency manifest, and documentation, which is materially broader than simple model export.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The code hard-codes a China-specific holiday locale in `create_holiday_features(..., country='CN')` and also sets the CLI default for `--country` to `CN`. This imposes a specific locale by default rather than prompting for or clearly requiring user selection, which can violate language/locale policy expectations when used in broader contexts.

Description-Behavior Mismatch

Low
Confidence
98% confidence
Finding
The module docstring claims support for GRU, Transformer, and Prophet models, but the CLI only accepts lightgbm, xgboost, random_forest, lstm, and sarima. This creates a manifest/code behavior mismatch because the file presents broader forecasting capabilities than it actually implements.

Missing User Warnings

Low
Confidence
84% confidence
Finding
This code performs multiple file-write operations, including creating the output directory and saving model, metrics, and config artifacts. Although it logs the saved paths after writing, there is no prior disclosure or warning that running the script will modify the local filesystem under the selected output directory.

Intent-Code Divergence

Low
Confidence
77% confidence
Finding
The inline comments distinguish LSTM sequence handling from tree-model flattening, but both branches call the same create_sequences and split functions without any flattening at that stage. The actual flattening for tree models happens later inside individual training functions, so the comment actively misdescribes what this code block does.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/hyperparameter_search.py:171

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/train_model.py:237