diff --git a/scanner/skillhub_scanner_app.py b/scanner/skillhub_scanner_app.py index c008d17e2..9d6ad01a3 100644 --- a/scanner/skillhub_scanner_app.py +++ b/scanner/skillhub_scanner_app.py @@ -5,7 +5,9 @@ import inspect import logging import os import shutil +import sys import tempfile +from decimal import Decimal, InvalidOperation from functools import wraps from importlib import import_module from pathlib import Path @@ -17,6 +19,25 @@ _RUNTIME_TEMP_ROOT = Path( ) +def _parse_upload_size_bytes(value: str) -> int: + """Accept exact integral scientific notation emitted by Helm for byte limits.""" + try: + return max(1, int(value)) + except ValueError: + if "e" not in value.lower(): + raise + + try: + amount = Decimal(value) + except InvalidOperation as exc: + raise ValueError("Scanner upload size must be an integer") from exc + if not amount.is_finite() or amount != amount.to_integral_value() or amount > sys.maxsize: + raise ValueError("Scanner upload size must be a finite supported integer") + if amount <= 1: + return 1 + return int(amount) + + def _prepare_runtime_temp_root() -> None: """Recreate the scanner-owned temp root before upstream allocates request directories.""" if _RUNTIME_TEMP_ROOT.name != "skillhub-scanner-runtime" or _RUNTIME_TEMP_ROOT.is_symlink(): @@ -41,8 +62,8 @@ from skill_scanner.cli import cli as _upstream_cli _upstream_router = import_module("skill_scanner.api.router") _MAX_CONCURRENT_SCANS = max(1, int(os.getenv("SKILLHUB_SCANNER_MAX_CONCURRENT_SCANS", "1"))) _HARD_TIMEOUT_SECONDS = max(1, int(os.getenv("SKILLHUB_SCANNER_HARD_TIMEOUT_SECONDS", "930"))) -_upstream_router.MAX_UPLOAD_SIZE_BYTES = max( - 1, int(os.getenv("SKILLHUB_SCANNER_MAX_UPLOAD_SIZE_BYTES", "110100480")) +_upstream_router.MAX_UPLOAD_SIZE_BYTES = _parse_upload_size_bytes( + os.getenv("SKILLHUB_SCANNER_MAX_UPLOAD_SIZE_BYTES", "110100480") ) _active_scans = 0 _active_scans_guard = asyncio.Lock() diff --git a/scanner/tests/test_skillhub_scanner_app.py b/scanner/tests/test_skillhub_scanner_app.py index 2c3417065..580cc278d 100644 --- a/scanner/tests/test_skillhub_scanner_app.py +++ b/scanner/tests/test_skillhub_scanner_app.py @@ -236,6 +236,17 @@ class SkillHubScannerAppTest(unittest.IsolatedAsyncioTestCase): self.assertEqual(123456, module._router_stub.MAX_UPLOAD_SIZE_BYTES) + async def test_upload_limit_accepts_integral_scientific_notation(self): + module = _load_module({"SKILLHUB_SCANNER_MAX_UPLOAD_SIZE_BYTES": "1.1010048e+08"}) + self.addCleanup(shutil.rmtree, module._test_temp_parent) + + self.assertEqual(110100480, module._router_stub.MAX_UPLOAD_SIZE_BYTES) + + async def test_upload_limit_rejects_invalid_scientific_notation(self): + for value in ("1.5e+0", "1e-1", "1e+", "1e999999", "NaN", "Infinity"): + with self.subTest(value=value), self.assertRaises(ValueError): + self.module._parse_upload_size_bytes(value) + async def test_upload_limit_is_at_least_one_byte(self): module = _load_module({"SKILLHUB_SCANNER_MAX_UPLOAD_SIZE_BYTES": "0"}) self.addCleanup(shutil.rmtree, module._test_temp_parent)