T09 · Insecure Skill Coding Practices
- Location
stock_skill/models.py:21- Finding
Unsafe Joblib deserialization with bypassable path containment
- Content
View full analysis
Vulnerability Details
File Location:
stock_skill/models.py:21-94
Vulnerability Type: Unsafe deserialization and insufficient path validation
Risk Level: HighVulnerable Code
python def _validate_model_path(file_path: str) -> None: abs_path = Path(file_path).resolve() if not str(abs_path).startswith(str(_MODELS_DIR.resolve())): raise ValueError( f"Security restriction: model files must be located under {_MODELS_DIR}." f"Attempted path: {file_path}" ) if abs_path.suffix not in _ALLOWED_EXTENSIONS: raise ValueError( f"Security restriction: extension '{abs_path.suffix}' is not allowed." ) if not abs_path.exists(): raise FileNotFoundError(f"Model file does not exist: {file_path}")python def load_model_safe(file_path: str, loader_func=None) -> Optional[Any]: try: _validate_model_path(file_path) if not _check_file_integrity(file_path): logger.error(f"Model integrity check failed: {file_path}") return None if loader_func is None: import joblib loader_func = joblib.load model = loader_func(file_path) return model except Exception as e: logger.error(f"Model loading failed: {e}") return NoneTechnical Analysis
joblib.load()uses Python pickle-compatible deserialization. Pickle data may contain reduction instructions that import modules and invoke arbitrary callables during deserialization. Loading a malicious Joblib file therefore amounts to executing code with the privileges of the running process.The
_check_file_integrity()routine does not establish authenticity or integrity. It only limits file size and checks the filename for selected substrings. It does not compare a cryptographic digest, validate a digital signature, inspect ownership, ...[truncated 2014 chars]- Remediation
View remediation
Remediation Suggestions
-
Replace the string-prefix check with a component-aware containment check:
python trusted_root = _MODELS_DIR.resolve(strict=True) candidate = Path(file_path).resolve(strict=True) if not candidate.is_relative_to(trusted_root): raise ValueError("Model path is outside the trusted model directory") -
Verify that the candidate is a regular file and reject symbolic links or other special files where appropriate:
python if candidate.is_symlink() or not candidate.is_file(): raise ValueError("Model must be a regular, non-symlink file") -
Avoid pickle and Joblib for files that are not guaranteed to be trusted. Prefer non-executable formats such as safetensors or a strictly validated JSON representation.
-
If Joblib must remain supported, require a cryptographic signature or an allowlisted SHA-256 digest generated through a trusted model-build process.
-
Store trusted models in a directory that is not writable by untrusted users or unrelated application components.
-
Remove
.pkland.joblibfrom the allowed extension list when executable serialization is unnecessary. -
Add tests covering sibling-prefix paths, symbolic links, replaced model files, and malformed serialized content.
-
