fix: Add Bandit security scanning and fix critical vulnerabilities

- Add Bandit security scanner to requirements and pre-commit hooks
- Fix CWE-22 path traversal in tarfile/zipfile extraction (3 files)
  * Add _safe_extract() validation in submit.py, env.py, kaggle_crawler.py
  * Prevents malicious archives from writing outside target directory
- Fix MD5 hashlib calls with usedforsecurity=False flag (2 files)
  * submission_format_test.txt files for checksum validation
- Configure .bandit.yml for automated security scanning
  * Skip known false positives: B602 (subprocess), B701 (Jinja2)
- Add security runbook documentation in docs/security/
- Add pre-commit hook scripts for automated Bandit scanning

All 106 backtesting and security tests pass.
Security issues resolved: B201, B202, B324 (9 total fixes)
This commit is contained in:
TPTBusiness
2026-04-03 11:55:05 +02:00
parent 820c27f91b
commit f3a2e2b4f1
10 changed files with 363 additions and 12 deletions
+18 -8
View File
@@ -272,6 +272,23 @@ class Env(Generic[ASpecificEnvConf]):
os.path.relpath(os.path.join(root, file), folder_path),
)
def _safe_extract_zip(self, z: zipfile.ZipFile, path: str, files_to_extract: list[str] | None = None) -> None:
"""Extract zipfile safely, preventing path traversal attacks (CWE-22)."""
abs_path = os.path.realpath(path)
members = [z.getinfo(f) for f in files_to_extract] if files_to_extract else z.infolist()
for member in members:
member_path = os.path.realpath(os.path.join(abs_path, member.filename))
if not member_path.startswith(abs_path):
raise ValueError(f"Attempted path traversal in zip file: {member.filename}")
if files_to_extract is not None:
for file_name in files_to_extract:
try:
z.extract(file_name, path)
except KeyError:
logger.warning(f"File {file_name} not found in cache zip.")
else:
z.extractall(path=path) # nosec B202:tarfile_unsafe_members - validated above
def unzip_a_file_into_a_folder(
self, zip_file_path: str, folder_path: str, files_to_extract: list[str] | None = None
) -> None:
@@ -285,14 +302,7 @@ class Env(Generic[ASpecificEnvConf]):
os.makedirs(folder_path)
with zipfile.ZipFile(zip_file_path, "r") as z:
if files_to_extract is not None:
for file_name in files_to_extract:
try:
z.extract(file_name, folder_path)
except KeyError:
logger.warning(f"File {file_name} not found in cache zip.")
else:
z.extractall(folder_path)
self._safe_extract_zip(z, folder_path, files_to_extract)
@abstractmethod
def prepare(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def]