Files
computer-playbook/tests/unit/python/cli/contributing/security/test_runtimes.py
T
kevinveenbirkenbachandClaude Opus 5.5 d116dde659 fix(sys-ctl-hlth-runtime): bound the engine calls and catch hidden PHP runtimes
Three CodeRabbit findings on PR #1265:

- script.py listed the running containers with `container ps` and
  `container inspect` without a timeout, so a hung engine kept the daily
  unit active forever and blocked the next timer run. Both calls now use
  EXEC_TIMEOUT_SECONDS; main turns a timeout or engine error into a
  one-line failure and exit 1 instead of a traceback.

- runtime.py's PHP_PACKAGE_PATTERN requires a digit after "php", so an
  image installing an unversioned interpreter package (`php-cli`) and
  setting no PHP_VERSION was reported as PHP-free. The interpreter
  packages php, php-cli, php-fpm, php-cgi, php-phpdbg and
  libapache2-mod-php are now recorded as PhpRuntime.packages and reported
  with an unknown patch level; extension and library packages
  (php-mysql, php-pear, php-common, phpmyadmin) and paths do not count.

- php_finding returned early for a patched PHP_VERSION, so an end-of-life
  package line installed next to it (e.g. php7) was never reported. The
  version only returns a finding now; otherwise the lines are judged too.

Every finding has unit tests that fail on the previous code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:32:57 +01:00

90 lines
3.1 KiB
Python

from __future__ import annotations
import contextlib
import io
import unittest
import cli.contributing.security.runtimes.__main__ as runtimes_main
from utils.docker.image.discovery import ImageRef
FLOORS = {"8.2": "8.2.35", "8.3": "8.3.36", "8.4": "8.4.27", "8.5": "8.5.12"}
def _ref(role, service):
return ImageRef(
role=role,
service=service,
name=f"acme/{service}",
version="1.0",
source=f"docker.io/acme/{service}:1.0",
)
class TestReport(unittest.TestCase):
def _report(self, refs, probed):
with contextlib.redirect_stdout(io.StringIO()) as out:
code = runtimes_main.report(refs, probed, FLOORS)
return code, out.getvalue()
def test_an_eol_image_fails_the_report(self):
code, output = self._report(
[_ref("web-app-akaunting", "akaunting")], [["ENV PHP_VERSION=8.1.34"]]
)
self.assertEqual(code, 1)
self.assertIn("runs the end-of-life line 8.1", output)
def test_patched_images_and_images_without_php_pass(self):
code, _output = self._report(
[
_ref("web-app-wordpress", "wordpress"),
_ref("svc-db-postgres", "postgres"),
],
[["ENV PHP_VERSION=8.4.27"], ["ENV PG_VERSION=17.2"]],
)
self.assertEqual(code, 0)
def test_an_unanswered_image_fails_the_report(self):
code, output = self._report([_ref("web-app-lam", "lam")], [None])
self.assertEqual(code, 1)
self.assertIn(
"web-app-lam:lam (docker.io/acme/lam:1.0): the registry gave no answer",
output,
)
def test_a_distro_line_without_patch_level_is_listed_but_passes(self):
code, output = self._report(
[_ref("web-app-lam", "lam")], [["RUN apt-get install -y php8.4-ldap"]]
)
self.assertEqual(code, 0)
self.assertIn("PHP line 8.4: patch level not in the registry metadata", output)
def test_an_unversioned_php_package_is_listed_with_unknown_patch_level(self):
code, output = self._report(
[_ref("web-app-lam", "lam")], [["RUN apt-get install -y php-cli"]]
)
self.assertEqual(code, 0)
self.assertIn(
"PHP package php-cli: patch level not in the registry metadata", output
)
self.assertIn("1 ship PHP", output)
def test_an_eol_package_line_fails_next_to_a_patched_php_version(self):
code, output = self._report(
[_ref("web-app-lam", "lam")],
[["ENV PHP_VERSION=8.4.27", "RUN apk add php7 php7-fpm"]],
)
self.assertEqual(code, 1)
self.assertIn("PHP 8.4.27: line 7 is end of life", output)
def test_an_eol_php_base_argument_fails_the_report(self):
code, output = self._report(
[_ref("web-app-fusiondirectory", "fusiondirectory")],
[["ARG PHP_BASE=7.4", "RUN |1 PHP_BASE=7.4 /bin/bash -c install"]],
)
self.assertEqual(code, 1)
self.assertIn("PHP line 7.4: line 7.4 is end of life", output)
if __name__ == "__main__":
unittest.main()