Parse Nmap XML output for vulnerabilities
(self, xml_path: str, scan_id: str)
| 1817 | |
| 1818 | # Build command |
| 1819 | templates = options.get('templates', self.NUCLEI_FAST_TEMPLATES) |
| 1820 | severity_filter = options.get('severity', 'low,medium,high,critical') |
| 1821 | rate_limit = options.get('rate_limit', 150) |
| 1822 | |
| 1823 | # Warn early if templates are missing — otherwise nuclei is a no-op |
| 1824 | tpl_info = self.get_nuclei_template_info() |
| 1825 | if tpl_info.get('count', 0) == 0: |
| 1826 | self._scan_log(scan_id, 'warning', |
| 1827 | "No nuclei templates installed — triggering download. " |
| 1828 | "This scan may report nothing until templates finish installing.") |
| 1829 | threading.Thread(target=self.ensure_nuclei_templates, daemon=True).start() |
| 1830 | else: |
| 1831 | self._scan_log(scan_id, 'info', |
| 1832 | f"Using {tpl_info['count']} nuclei templates (version {tpl_info.get('version') or 'unknown'})") |
| 1833 | |
| 1834 | with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as output_file: |
| 1835 | output_path = output_file.name |
| 1836 | stderr_path = output_path + '.err' |
| 1837 | |
| 1838 | # Scale nuclei's memory/CPU footprint to the board so a small one |
| 1839 | # (e.g. a 512MB Pi Zero 2 W) doesn't OOM and lock up. |
| 1840 | perf_flags, nuclei_env, severity_filter, tuning_note = \ |
| 1841 | self._nuclei_resource_tuning(rate_limit, severity_filter) |
| 1842 | if tuning_note: |
| 1843 | self._scan_log(scan_id, 'info', f"Nuclei tuning — {tuning_note}") |
| 1844 | |
| 1845 | # On a constrained board, refuse to start if free RAM is already too |
| 1846 | # low — better a clean failure with the numbers than a locked-up Pi. |
| 1847 | precheck_error = self._nuclei_memory_precheck(nuclei_env) |
| 1848 | if precheck_error: |
| 1849 | self._scan_log(scan_id, 'error', precheck_error) |
| 1850 | progress.error_message = precheck_error |
| 1851 | raise RuntimeError(precheck_error) |
| 1852 | |
| 1853 | gomem_mib = None |
| 1854 | _gm = re.match(r'(\d+)MiB', nuclei_env.get('GOMEMLIMIT', '')) |
| 1855 | if _gm: |
| 1856 | gomem_mib = int(_gm.group(1)) |
| 1857 | |
| 1858 | # If a GOMEMLIMIT tier applies and the kernel's memory cgroup is enabled, |
| 1859 | # wrap nuclei in a systemd-run scope with a HARD MemoryMax and swap off, |
| 1860 | # so a runaway is OOM-killed cleanly instead of taking the Pi with it. |
| 1861 | cgroup_prefix = [] |
| 1862 | use_session = False |
| 1863 | if gomem_mib and self._memory_cgroup_available(): |
| 1864 | hard_mib = int(gomem_mib * 1.5) |
no test coverage detected