scanfuse 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. scanfuse-0.2.0/.editorconfig +15 -0
  2. scanfuse-0.2.0/.gitignore +15 -0
  3. scanfuse-0.2.0/CHANGELOG.md +79 -0
  4. scanfuse-0.2.0/CONTRIBUTING.md +19 -0
  5. scanfuse-0.2.0/LICENSE +21 -0
  6. scanfuse-0.2.0/PKG-INFO +208 -0
  7. scanfuse-0.2.0/README.md +173 -0
  8. scanfuse-0.2.0/SECURITY.md +23 -0
  9. scanfuse-0.2.0/SECURITY_MODEL.md +57 -0
  10. scanfuse-0.2.0/build.py +23 -0
  11. scanfuse-0.2.0/docs/images/report-finding.png +0 -0
  12. scanfuse-0.2.0/docs/images/report-summary.png +0 -0
  13. scanfuse-0.2.0/docs/images/scanfuse-flow.svg +1 -0
  14. scanfuse-0.2.0/examples/README.md +21 -0
  15. scanfuse-0.2.0/examples/sample-report/.scanfuse-manifest-markdown.json +45 -0
  16. scanfuse-0.2.0/examples/sample-report/asymmetric-private-key.md +40 -0
  17. scanfuse-0.2.0/examples/sample-report/attacker-can-cause-kyverno-user-to-unintentionally-consume-insecure-image.md +61 -0
  18. scanfuse-0.2.0/examples/sample-report/cilium-has-insecure-ipsec-transport-encryption.md +69 -0
  19. scanfuse-0.2.0/examples/sample-report/cilium-leaks-sensitive-information-in-cilium-bugtool.md +73 -0
  20. scanfuse-0.2.0/examples/sample-report/cilium-s-cidr-deny-policies-may-not-take-effect-when-a-more-narrow-cidr-allow-is-present.md +63 -0
  21. scanfuse-0.2.0/examples/sample-report/cosign-malicious-artifects-can-cause-machine-wide-denial-of-service.md +71 -0
  22. scanfuse-0.2.0/examples/sample-report/cosign-malicious-attachments-can-cause-system-wide-denial-of-service.md +71 -0
  23. scanfuse-0.2.0/examples/sample-report/cross-site-request-forgery.md +436 -0
  24. scanfuse-0.2.0/examples/sample-report/cross-site-scripting.md +258 -0
  25. scanfuse-0.2.0/examples/sample-report/directory-browsing.md +44 -0
  26. scanfuse-0.2.0/examples/sample-report/external-service-interaction-dns.md +73 -0
  27. scanfuse-0.2.0/examples/sample-report/go-retryablehttp-url-might-write-sensitive-information-to-log-file.md +87 -0
  28. scanfuse-0.2.0/examples/sample-report/golang-net-http-x-net-http2-unlimited-number-of-continuation-frames-causes-dos.md +127 -0
  29. scanfuse-0.2.0/examples/sample-report/index.md +286 -0
  30. scanfuse-0.2.0/examples/sample-report/intermittent-http-policy-bypass.md +67 -0
  31. scanfuse-0.2.0/examples/sample-report/jose-go-improper-handling-of-highly-compressed-data.md +166 -0
  32. scanfuse-0.2.0/examples/sample-report/jwt-token.md +73 -0
  33. scanfuse-0.2.0/examples/sample-report/kyverno-resource-with-a-deletiontimestamp-may-allow-policy-circumvention.md +61 -0
  34. scanfuse-0.2.0/examples/sample-report/kyverno-s-policyexception-objects-can-be-created-in-any-namespace-by-default.md +59 -0
  35. scanfuse-0.2.0/examples/sample-report/kyverno-seccomp-control-can-be-circumvented.md +63 -0
  36. scanfuse-0.2.0/examples/sample-report/kyverno-vulnerable-due-to-usage-of-insecure-cipher.md +106 -0
  37. scanfuse-0.2.0/examples/sample-report/mholt-archiver-path-traversal-vulnerability.md +62 -0
  38. scanfuse-0.2.0/examples/sample-report/microsoft-windows-remote-desktop-protocol-server-man-in-the-middle-weakness.md +95 -0
  39. scanfuse-0.2.0/examples/sample-report/moby-authz-zero-length-regression.md +93 -0
  40. scanfuse-0.2.0/examples/sample-report/opa-opa-smb-force-authentication.md +69 -0
  41. scanfuse-0.2.0/examples/sample-report/path-traversal.md +79 -0
  42. scanfuse-0.2.0/examples/sample-report/policy-bypass-for-host-firewall-policy-due-to-race-condition-in-cilium-agent.md +67 -0
  43. scanfuse-0.2.0/examples/sample-report/report.html +2565 -0
  44. scanfuse-0.2.0/examples/sample-report/smb-signing-disabled.md +80 -0
  45. scanfuse-0.2.0/examples/sample-report/sql-injection-fingerprinting.md +62 -0
  46. scanfuse-0.2.0/examples/sample-report/sql-injection.md +63 -0
  47. scanfuse-0.2.0/examples/sample-report/ssl-certificate.md +100 -0
  48. scanfuse-0.2.0/examples/sample-report/ssl-scanner-breach.md +43 -0
  49. scanfuse-0.2.0/examples/sample-report/ssl-scanner-offer-sslv3.md +43 -0
  50. scanfuse-0.2.0/examples/sample-report/ssl-scanner-poodle-sslv3.md +45 -0
  51. scanfuse-0.2.0/examples/sample-report/strict-transport-security-misconfiguration.md +55 -0
  52. scanfuse-0.2.0/examples/sample-report/terminal-services-encryption-level-is-medium-or-low.md +103 -0
  53. scanfuse-0.2.0/examples/sample-report/unencrypted-ingress-health-traffic-when-using-wireguard-transparent-encryption.md +63 -0
  54. scanfuse-0.2.0/examples/sample-report/unencrypted-traffic-between-nodes-when-using-ipsec-and-l7-policies.md +65 -0
  55. scanfuse-0.2.0/examples/sample-report/unencrypted-traffic-between-nodes-when-using-wireguard-and-l7-policies.md +65 -0
  56. scanfuse-0.2.0/examples/sample-report/unencrypted-traffic-between-pods-when-using-wireguard-and-an-external-kvstore.md +65 -0
  57. scanfuse-0.2.0/pyproject.toml +72 -0
  58. scanfuse-0.2.0/scanfuse/__init__.py +4 -0
  59. scanfuse-0.2.0/scanfuse/cli.py +235 -0
  60. scanfuse-0.2.0/scanfuse/engine.py +503 -0
  61. scanfuse-0.2.0/scanfuse/html_report.py +23 -0
  62. scanfuse-0.2.0/scanfuse/models.py +54 -0
  63. scanfuse-0.2.0/scanfuse/parsers/__init__.py +3 -0
  64. scanfuse-0.2.0/scanfuse/parsers/base.py +142 -0
  65. scanfuse-0.2.0/scanfuse/parsers/burp.py +135 -0
  66. scanfuse-0.2.0/scanfuse/parsers/nessus.py +126 -0
  67. scanfuse-0.2.0/scanfuse/parsers/nmap.py +137 -0
  68. scanfuse-0.2.0/scanfuse/parsers/nuclei.py +157 -0
  69. scanfuse-0.2.0/scanfuse/parsers/trivy.py +187 -0
  70. scanfuse-0.2.0/scanfuse/parsers/zap.py +124 -0
  71. scanfuse-0.2.0/scanfuse/templates/finding.md.j2 +67 -0
  72. scanfuse-0.2.0/scanfuse/templates/index.md.j2 +44 -0
  73. scanfuse-0.2.0/scanfuse/utils.py +184 -0
  74. scanfuse-0.2.0/scanfuse.spec +40 -0
  75. scanfuse-0.2.0/tests/conftest.py +11 -0
  76. scanfuse-0.2.0/tests/fixtures/dummy_burp.xml +20 -0
  77. scanfuse-0.2.0/tests/fixtures/dummy_nessus.xml +21 -0
  78. scanfuse-0.2.0/tests/fixtures/dummy_nmap_clean.xml +23 -0
  79. scanfuse-0.2.0/tests/fixtures/dummy_nmap_dirty.xml +19 -0
  80. scanfuse-0.2.0/tests/fixtures/dummy_nuclei.json +2 -0
  81. scanfuse-0.2.0/tests/fixtures/dummy_trivy.json +47 -0
  82. scanfuse-0.2.0/tests/fixtures/dummy_zap.xml +43 -0
  83. scanfuse-0.2.0/tests/fixtures/public/LICENSE-DefectDojo-sample-scan-files +21 -0
  84. scanfuse-0.2.0/tests/fixtures/public/README.md +15 -0
  85. scanfuse-0.2.0/tests/fixtures/public/burpvulns_v2_1_02.xml +336 -0
  86. scanfuse-0.2.0/tests/fixtures/public/juice_shop-trivy-report.json +196 -0
  87. scanfuse-0.2.0/tests/fixtures/public/k-client_trivy-report.json +1392 -0
  88. scanfuse-0.2.0/tests/fixtures/public/nessus_v_unknown_two_smallest_hosts.nessus +3163 -0
  89. scanfuse-0.2.0/tests/fixtures/public/nmap_output_v7.12.xml +39 -0
  90. scanfuse-0.2.0/tests/fixtures/public/nmap_v6.40.xml +24 -0
  91. scanfuse-0.2.0/tests/fixtures/public/trivy_mix.json +101 -0
  92. scanfuse-0.2.0/tests/fixtures/public/zaproxy-demo-site_1.4.0.1.xml +1543 -0
  93. scanfuse-0.2.0/tests/fixtures/real_nmap_closed_ports.xml +20 -0
  94. scanfuse-0.2.0/tests/fixtures/real_nmap_os_detection.xml +36 -0
  95. scanfuse-0.2.0/tests/test_cli.py +97 -0
  96. scanfuse-0.2.0/tests/test_data_integrity.py +95 -0
  97. scanfuse-0.2.0/tests/test_html_export.py +83 -0
  98. scanfuse-0.2.0/tests/test_identifiers.py +61 -0
  99. scanfuse-0.2.0/tests/test_large_input.py +28 -0
  100. scanfuse-0.2.0/tests/test_merge.py +128 -0
  101. scanfuse-0.2.0/tests/test_packaged_cli.py +98 -0
  102. scanfuse-0.2.0/tests/test_parser_regressions.py +82 -0
  103. scanfuse-0.2.0/tests/test_parsers.py +92 -0
  104. scanfuse-0.2.0/tests/test_properties.py +398 -0
  105. scanfuse-0.2.0/tests/test_public_samples.py +149 -0
  106. scanfuse-0.2.0/tests/test_qa_cases.py +66 -0
  107. scanfuse-0.2.0/tests/test_real_scans.py +67 -0
  108. scanfuse-0.2.0/tests/test_report_behavior.py +416 -0
  109. scanfuse-0.2.0/tools/check_public_samples.py +100 -0
  110. scanfuse-0.2.0/tools/large_check.py +136 -0
  111. scanfuse-0.2.0/uv.lock +960 -0
@@ -0,0 +1,15 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.{yml,yaml,json}]
12
+ indent_size = 2
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv*/
4
+ .runtime/
5
+ .pytest*/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ node_modules/
9
+ build/
10
+ dist/
11
+ *.egg-info/
12
+ .env
13
+ .env.*
14
+ !.env.example
15
+ .hypothesis/
@@ -0,0 +1,79 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 (2026-11-07)
4
+
5
+ The project is renamed from `repx` to `scanfuse` (the name `repx` is taken on PyPI). The command, the Python package, the configuration file (`.repx.yaml` is now `.scanfuse.yaml`) and the environment variable for the packaged-program tests (`REPX_EXE` is now `SCANFUSE_EXE`) changed. repx 0.1 was never published.
6
+
7
+ This version changes the JSON format and the merge rules. Read "Changed" before you update scripts.
8
+
9
+ ### Changed
10
+
11
+ * **`report.json` is now an object**, not a list: `{"schema_version": 2, "complete": true, "errors": [], "findings": [...]}`. Scripts that read the list must read `["findings"]` instead.
12
+ * **Merging is stricter.** Findings are merged only when the titles are equal (ignoring case and surrounding spaces), they have the same CVE or GHSA ids and the same port, and the same tool does not give them different tool ids. Similar titles are no longer merged. A merged finding keeps every host and every piece of evidence, and the description, remediation, severity, CVSS score and vector of each tool are listed in `sources`.
13
+ * CSV: `Hosts` shows `ip (host name)` when a host name is known. Six columns were added after the original eight: `CVSS Vector`, `Port`, `Identifiers`, `References`, `Other Reports`, `Import Status`.
14
+ * `--host` matches the IP or host name exactly (ignoring case). Before, it matched any part of the text, so `10.0.0.1` also matched `10.0.0.10`. `*` and `?` are wildcards.
15
+ * `--min-severity`, `--format`, `--format-out` accept only their documented values. `--template-dir` must exist. A wrong value now stops with exit code 2.
16
+ * An invalid `.scanfuse.yaml` (wrong types, not a mapping, YAML syntax error) is an error: exit code 1 and an incomplete report.
17
+ * The Markdown index lists findings by severity (before: alphabetically by severity name).
18
+ * Titles, host names, ids and tool names are escaped in the Markdown, so scanner text cannot add links or headings there. Descriptions keep their formatting but cannot create headings.
19
+ * Version 0.2.0. The version is defined only in `scanfuse/__init__.py`.
20
+
21
+ ### Added
22
+
23
+ * **References and CWE ids.** Links that the scanner gives for a finding (ZAP `reference`, Burp `references`, Nessus `see_also`, Trivy `References` and `PrimaryURL`, Nuclei `info.reference`) are kept in the new field `references`. They appear under "References" in the Markdown and HTML report, in the JSON, and in the new CSV column `References`. CWE ids (ZAP `cweid`, Burp classifications, Trivy `CweIDs`) are identifiers. WASC ids from ZAP too.
24
+ * Nessus: the synopsis is put in front of the description, CVSS v3 is used if there is no v2 score, and the placeholder solution "n/a" is no longer shown as a remediation. Burp: confidence and location are kept in an evidence entry.
25
+ * `tools/check_public_samples.py` downloads the twelve public files, compares the number of findings with an independent count, merges, exports in all formats and prints the time. All twelve pass; the largest (2,587 ZAP alerts, 1,427 Trivy findings) take 1.4 s.
26
+ * Existing reports are protected. scanfuse stops with exit code 2 if files would be overwritten. With `--replace`, it builds the new report in a temporary folder and attempts to restore the old report if publication fails. A failed recovery retains backups; a process or system crash can leave a mixed report.
27
+ * A Nuclei JSONL file with malformed lines retains valid findings and marks the report incomplete.
28
+ * Reports from partly failed runs are marked: `complete: false` and `errors` in the JSON, an INCOMPLETE notice in `index.md` and `report.html`, and an `Import Status` column in the CSV.
29
+ * Ids and ports are kept: CVE, GHSA and CWE ids, and tool ids such as `nessus-plugin:12345`, as `identifiers`, and the port as `port`.
30
+ * Evidence records which tool reported it when findings are merged.
31
+ * `--csv-excel` (byte order mark, `;` separator), `--replace`, `--version`.
32
+ * MIT `LICENSE`, package metadata, a source package without internal files.
33
+ * `tools/large_check.py` for large inputs. CI builds and tests standalone programs on Linux, Windows and macOS, runs the tests on Python 3.12 and 3.13, checks the dependencies with pip-audit and installs the wheel in a clean environment.
34
+
35
+ ### Fixed
36
+
37
+ Found by running scanfuse on twelve real scanner files from the public [DefectDojo sample-scan-files](https://github.com/DefectDojo/sample-scan-files) repository (MIT license): Nmap 6.40 and 7.12, ZAP 1.4, Trivy (three schemas), Nessus, Burp.
38
+
39
+ * **Burp files were not detected.** The format check read 1,024 characters, but Burp writes a long DTD in front of the first tag (`<issues>` starts at about byte 3,400). The file was then read as nmap and failed. scanfuse now reads 64 KB.
40
+ * **Older Trivy files were not read.** They are a plain JSON list of results without `SchemaVersion`. They were taken for Nuclei and failed. Both Trivy schemas are supported now.
41
+ * **ZAP 1.x reports gave one finding titled "Unknown Vulnerability".** Old ZAP files have `<alert>` instead of `<name>` and keep the URL, parameter and attack in the alert itself instead of in `<instances>`. In the sample, all 67 alerts had the same title, were merged into one, and lost their URLs. Now the title, the affected URL, the parameter, the attack and the other info are read. Newer ZAP files also keep `param`, `attack` and `otherinfo` of every instance now.
42
+ * Burp, ZAP and Nessus text fields contain HTML (`<p>`, `<br>`, `<li>`, `&quot;`). The report showed the markup as text. It is converted to plain text now, with line breaks and list items kept.
43
+ * nmap: the operating system guess was dropped. The guess with the highest accuracy is kept now, for example `Linux 5.0 - 5.7 (97%)`. The CPE names that version detection reports (`cpe:/a:...`) were dropped too and are kept as identifiers. Found by running nmap 7.94 against the local machine and comparing its XML with scanfuse's output.
44
+ * A scan without any open port (host down, all ports closed, list scan) ended without a word. scanfuse now says that the input contains no findings and that nothing was exported.
45
+ * Merging lost data: description, remediation, CVSS score and vector of the other tools, findings with the same title but different CVEs, packages or ports, and the second operating system of a host.
46
+ * A finding titled "Index" or "Report", or named like a Windows device (`CON`, `NUL`, `COM1`), overwrote or collided with other files.
47
+ * Evidence was escaped twice and changed (`<` became `&lt;`, three backticks became `'''`). It is now written unchanged inside a code fence that is longer than any backtick run in the data.
48
+ * Formula protection in CSV missed look-alike characters such as a full-width `=` and a leading no-break space.
49
+ * `-f nuclei` on a file of another format produced a bogus finding. It is now an error.
50
+ * A missing file was reported as "could not auto-detect format". A directory or an output path that is a file caused a traceback. XML with entities gave a raw exception name. Messages for standard input showed the name of a temporary file. Square brackets in a file name could break the output.
51
+ * The wheel and the source package had no license, and the source package contained local planning files with user paths.
52
+
53
+ ### Measured
54
+
55
+ Not a reference system: one Linux machine in the cloud (Ubuntu glibc 2.39, 4 CPUs, 16 GB). 100,000 results, 50,000 distinct titles, each repeated on another host with other evidence. All hosts, all evidence entries and the number of findings were checked after each run.
56
+
57
+ | Output | Time | Peak memory |
58
+ |---|---|---|
59
+ | JSON | 13.9 s | 909 MB |
60
+ | CSV | 10.4 s | 687 MB |
61
+ | Markdown with index and HTML | 123.9 s | 1040 MB |
62
+
63
+ ### Checked with real scanner output
64
+
65
+ * Nmap 7.94 was run against 127.0.0.1: service and version scan with scripts, OS detection, a scan with only closed ports, a host discovery scan and a list scan. Two of those files are in `tests/fixtures/` (`real_nmap_*`).
66
+ * Twelve public files from the DefectDojo sample repository (see above). Eight of them are in `tests/fixtures/public/` with their license; the rest are downloaded by `tools/check_public_samples.py`.
67
+ * Nuclei has no public sample file in that repository. The Nuclei parser is still tested only with hand-made files. A real Nuclei export would help.
68
+ * The sample files are old (2013 to 2024). Current versions of ZAP, Burp, Nessus and Trivy may have fields that these files do not show.
69
+
70
+ ### Not checked
71
+
72
+ * The HTML report in a browser by hand (search, severity filter, keyboard, narrow window).
73
+ * The CSV in Excel and LibreOffice. Only the file content was checked.
74
+ * Standalone programs on other Linux distributions (Debian, Kali, Fedora, Arch, Alpine), Linux on ARM and macOS on Intel.
75
+ * Whether the Windows program starts on a clean Windows 11 machine without developer tools. CI builds and tests it on a Windows runner.
76
+
77
+ ## 0.1.0 (named repx)
78
+
79
+ First version. Not published.
@@ -0,0 +1,19 @@
1
+ # Contributing
2
+
3
+ Issues and pull requests are welcome. For a larger change, open an issue first.
4
+
5
+ ## Set up and test
6
+
7
+ ```bash
8
+ uv sync --locked --extra dev
9
+ uv run ruff check .
10
+ uv run mypy scanfuse
11
+ uv run pytest tests/
12
+ ```
13
+
14
+ ## Rules
15
+
16
+ * Keep a change small and add a test that fails without it.
17
+ * Scanner files are untrusted input. A parser must not execute, fetch or trust anything in them.
18
+ * A new fixture must come from a public source with a license that allows redistribution, or be made by you. Remove hostnames, IP addresses and credentials first. Never add files from a real client engagement.
19
+ * Security problems: see [SECURITY.md](SECURITY.md), do not open a public issue.
scanfuse-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gencay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.5
2
+ Name: scanfuse
3
+ Version: 0.2.0
4
+ Summary: Fast, local-first pentest finding extractor & markdown report engine.
5
+ Project-URL: Repository, https://github.com/gencay-e/scanfuse
6
+ Project-URL: Changelog, https://github.com/gencay-e/scanfuse/blob/main/CHANGELOG.md
7
+ Author: Gencay
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: burp,nessus,nmap,nuclei,pentest,report,security,trivy,zap
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Information Technology
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Security
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: defusedxml<1,>=0.7.1
21
+ Requires-Dist: jinja2<4,>=3.1.3
22
+ Requires-Dist: markdown<4,>=3.11
23
+ Requires-Dist: nh3<1,>=0.3.7
24
+ Requires-Dist: pydantic<3,>=2.7.0
25
+ Requires-Dist: pyyaml<7,>=6.0.1
26
+ Requires-Dist: rich<16,>=13.7.0
27
+ Requires-Dist: typer<1,>=0.12.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: hypothesis>=6.100.0; extra == 'dev'
30
+ Requires-Dist: mypy>=1.9.0; extra == 'dev'
31
+ Requires-Dist: pyinstaller>=6.5.0; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.3.0; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # scanfuse
37
+
38
+ A local command-line tool that reads pentest scanner reports and writes one report from them.
39
+
40
+ <img alt="Scan files from Nmap, Nuclei, ZAP, Burp, Nessus and Trivy go into scanfuse, which writes index.md, report.html, report.json and report.csv" src="docs/images/scanfuse-flow.svg" width="900">
41
+
42
+ It imports Nmap, Nuclei, ZAP, Burp, Nessus and Trivy files and writes Markdown (with an index and a standalone HTML page), JSON or CSV. It runs offline and sends nothing anywhere.
43
+
44
+ Version 0.2.0, a pre-release. See [CHANGELOG.md](CHANGELOG.md).
45
+
46
+ ## Example
47
+
48
+ <img alt="The summary of a report" src="docs/images/report-summary.png" width="900">
49
+
50
+ <img alt="A finding in the report" src="docs/images/report-finding.png" width="900">
51
+
52
+ `report.html` follows the light or dark setting of your system; the screenshots show the dark one.
53
+
54
+ The report above was made from six real scan files (Nmap, ZAP, Trivy, Burp, Nessus). The input files, the
55
+ command and the full report are in [examples/](examples/).
56
+
57
+ ## Install
58
+
59
+ You need Python 3.12 or newer.
60
+
61
+ ```bash
62
+ pipx install scanfuse # from PyPI
63
+ pipx install git+https://github.com/gencay-e/scanfuse # latest state from GitHub
64
+ ```
65
+
66
+ `pip install scanfuse` works as well. From a checkout of this repository: `pipx install .`
67
+
68
+ Ready-made programs for Linux, Windows and macOS (no Python needed) are on the [Releases page](https://github.com/gencay-e/scanfuse/releases), with a `SHA256SUMS` file. To build one yourself, see [Build a standalone program](#build-a-standalone-program).
69
+
70
+ ## Use
71
+
72
+ ```bash
73
+ scanfuse scan.xml -o report
74
+ scanfuse nmap.xml nuclei.json zap.xml -o report
75
+ scanfuse scan.json -f trivy --format-out json -o report
76
+ scanfuse scan.xml --min-severity High --host 192.0.2.1 -o report
77
+ cat scan.json | scanfuse -o report
78
+ ```
79
+
80
+ `scanfuse` takes one or more files, or `-` for standard input. It finds the format by itself. Use `-f` to choose one: `nmap`, `nuclei`, `zap`, `burp`, `nessus`, `trivy`.
81
+
82
+ | Option | Meaning |
83
+ |---|---|
84
+ | `-o`, `--output` | Output directory. Default `report`. |
85
+ | `--format-out` | `markdown` (default), `json` or `csv`. `markdown` writes one file per finding, `index.md` and `report.html`. |
86
+ | `-s`, `--min-severity` | `Critical`, `High`, `Medium`, `Low` or `Info`. Case does not matter. |
87
+ | `--host` | Only findings on this host. Must match the IP or host name exactly, ignoring case. `*` and `?` are wildcards, for example `10.0.0.*`. |
88
+ | `-t`, `--template-dir` | Folder with your own `finding.md.j2` and `index.md.j2`. The folder must exist. Missing files fall back to the built-in templates. |
89
+ | `--replace` | Overwrite an existing report. See below. |
90
+ | `--csv-excel` | CSV with a byte order mark and `;` as separator, which German-language Excel expects. |
91
+ | `--version` | Print the version. |
92
+
93
+ ### Existing reports
94
+
95
+ `scanfuse` never overwrites files by accident. If the output directory already contains a file that the new report would write, it stops with exit code 2 and changes nothing. Add `--replace` to overwrite the report. With `--replace`, files of the earlier report that are no longer part of it are removed. Files that scanfuse did not write are left alone.
96
+
97
+ The report is built in a temporary folder first. If moving the files fails, scanfuse tries to restore the old report. If restoration also fails, the error gives the location of the retained backup. A process or system crash during replacement can still leave a mixed report.
98
+
99
+ ### Incomplete runs
100
+
101
+ If one input file cannot be read, scanfuse still writes the findings from the other files and exits with code 1. The report says so:
102
+
103
+ * `report.json`: `"complete": false` and the list of `errors`.
104
+ * `index.md` and `report.html`: an **INCOMPLETE** notice with the errors.
105
+ * `report.csv`: the column `Import Status` says `incomplete (...)` in every row.
106
+
107
+ An invalid `.scanfuse.yaml` counts as an error too. The report is then incomplete, because ignore rules were not applied.
108
+
109
+ ### Exit codes
110
+
111
+ | Code | Meaning |
112
+ |---|---|
113
+ | 0 | Complete. |
114
+ | 1 | Incomplete or failed: an input could not be read, `.scanfuse.yaml` is invalid, nothing could be written, or no input was given. |
115
+ | 2 | Invalid option, or the output files already exist (use `--replace`). |
116
+
117
+ ## Merging duplicates
118
+
119
+ Findings from several files are merged when they are the same finding:
120
+
121
+ * the titles are equal, ignoring case and surrounding spaces,
122
+ * they carry the same CVE or GHSA ids,
123
+ * they have the same port, and
124
+ * the same tool does not give them different tool ids (for example two Nessus plugin ids).
125
+
126
+ Titles that are only similar are not merged. A merged finding keeps all hosts and all evidence. Evidence shows which tool reported it. What each tool wrote (description, remediation, severity, CVSS score and vector, ids) is listed under **Reported by** in the Markdown and in `sources` in the JSON. The summary severity is the highest of all reports. The summary CVSS score is the highest score, together with its own vector.
127
+
128
+ ## Output
129
+
130
+ ### JSON
131
+
132
+ `report.json` is an object:
133
+
134
+ ```json
135
+ {
136
+ "schema_version": 2,
137
+ "complete": true,
138
+ "errors": [],
139
+ "findings": [ { "title": "...", "severity": "High", "identifiers": ["CVE-2024-0001"], "port": "443/tcp", "sources": [] } ]
140
+ }
141
+ ```
142
+
143
+ ### CSV
144
+
145
+ Columns: `Title`, `Severity`, `CVSS`, `Hosts`, `Source Tool`, `Description`, `Remediation`, `Evidence`, `CVSS Vector`, `Port`, `Identifiers`, `References`, `Other Reports`, `Import Status`. `Hosts` shows `ip (host name)` when a host name is known.
146
+
147
+ Cells that start with `=`, `+`, `-` or `@` get a leading `'` to reduce the risk of spreadsheet formulas. This also covers look-alike characters such as a full-width `=` and a leading no-break space. These cases were tested by reading the files back.
148
+
149
+ In Excel, import `--csv-excel` output with **Data > From Text/CSV** and use the semicolon delimiter. In a Windows Excel test, this preserved a multiline evidence field; the older Text Import Wizard split it across rows. Formula handling and LibreOffice remain unverified in those applications.
150
+
151
+ ### Scanner text in the report
152
+
153
+ Scanner output is untrusted. scanfuse treats it like this:
154
+
155
+ * Titles, host names, ids and tool names are shown as plain text. They cannot add links, headings or HTML.
156
+ * Descriptions and remediation text keep Markdown formatting, including links. Raw HTML is escaped, and the text cannot create a heading. A link in a scanner description is shown as the scanner wrote it, so check it before you click.
157
+ * Evidence is shown unchanged in a code block.
158
+ * The HTML page removes scripts, images, frames and `javascript:` links.
159
+ * XML with entity declarations is rejected.
160
+
161
+ ## Configuration: `.scanfuse.yaml`
162
+
163
+ `scanfuse` reads `.scanfuse.yaml` from the current directory.
164
+
165
+ ```yaml
166
+ ignore_titles:
167
+ - "Wappalyzer"
168
+ - "Header Missing"
169
+ ```
170
+
171
+ Findings whose title contains one of these texts (ignoring case) are left out. `ignore_titles` must be a list of strings. A single string is read as a list with one entry. Anything else, a file that is not a mapping, or a YAML syntax error is reported as an error and the rules are not applied.
172
+
173
+ ## Build a standalone program
174
+
175
+ ```bash
176
+ uv sync --extra dev
177
+ uv run python build.py
178
+ ```
179
+
180
+ The program is written to `dist/`. To check it against the test suite:
181
+
182
+ ```bash
183
+ SCANFUSE_EXE=dist/scanfuse uv run pytest tests/test_packaged_cli.py # Linux, macOS
184
+ $env:SCANFUSE_EXE = (Resolve-Path .\dist\scanfuse.exe).Path # Windows PowerShell
185
+ ```
186
+
187
+ A program built on Linux depends on the C library of the system it was built on. Build on the oldest system you want to support.
188
+
189
+ ## Development
190
+
191
+ ```bash
192
+ uv sync --locked --extra dev
193
+ uv run ruff check .
194
+ uv run mypy scanfuse
195
+ uv run pytest tests/
196
+ ```
197
+
198
+ ## Limits
199
+
200
+ * scanfuse was tested with real output from nmap (7.94, 7.12, 6.40), ZAP 1.4, Burp, Nessus and Trivy (three file layouts), taken from the public [DefectDojo sample-scan-files](https://github.com/DefectDojo/sample-scan-files). Those files are old. Newer versions of the tools may add fields that scanfuse does not read yet. Nuclei has no real sample file in the tests. Open an issue with a cleaned sample if a file fails.
201
+ * Format detection looks at the start of the file. Use `-f` if it guesses wrong.
202
+ * The HTML page and the CSV file were checked by reading the files. They were not opened by hand in a browser, Excel or LibreOffice.
203
+ * 100,000 results were processed completely in all three output formats on one Linux machine (4 CPUs, 16 GB). JSON took 14 seconds, CSV 10 seconds, Markdown with HTML 124 seconds, and the peak memory was between 0.7 and 1.0 GB. The HTML page for that many findings is large. You can repeat the check with `python tools/large_check.py 100000`.
204
+ * Standalone programs are built and tested in CI on Ubuntu 22.04 (x86_64), Windows (x86_64) and macOS (arm64). Other Linux distributions, Linux on ARM and macOS on Intel were not tested.
205
+
206
+ ## License
207
+
208
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,173 @@
1
+ # scanfuse
2
+
3
+ A local command-line tool that reads pentest scanner reports and writes one report from them.
4
+
5
+ <img alt="Scan files from Nmap, Nuclei, ZAP, Burp, Nessus and Trivy go into scanfuse, which writes index.md, report.html, report.json and report.csv" src="docs/images/scanfuse-flow.svg" width="900">
6
+
7
+ It imports Nmap, Nuclei, ZAP, Burp, Nessus and Trivy files and writes Markdown (with an index and a standalone HTML page), JSON or CSV. It runs offline and sends nothing anywhere.
8
+
9
+ Version 0.2.0, a pre-release. See [CHANGELOG.md](CHANGELOG.md).
10
+
11
+ ## Example
12
+
13
+ <img alt="The summary of a report" src="docs/images/report-summary.png" width="900">
14
+
15
+ <img alt="A finding in the report" src="docs/images/report-finding.png" width="900">
16
+
17
+ `report.html` follows the light or dark setting of your system; the screenshots show the dark one.
18
+
19
+ The report above was made from six real scan files (Nmap, ZAP, Trivy, Burp, Nessus). The input files, the
20
+ command and the full report are in [examples/](examples/).
21
+
22
+ ## Install
23
+
24
+ You need Python 3.12 or newer.
25
+
26
+ ```bash
27
+ pipx install scanfuse # from PyPI
28
+ pipx install git+https://github.com/gencay-e/scanfuse # latest state from GitHub
29
+ ```
30
+
31
+ `pip install scanfuse` works as well. From a checkout of this repository: `pipx install .`
32
+
33
+ Ready-made programs for Linux, Windows and macOS (no Python needed) are on the [Releases page](https://github.com/gencay-e/scanfuse/releases), with a `SHA256SUMS` file. To build one yourself, see [Build a standalone program](#build-a-standalone-program).
34
+
35
+ ## Use
36
+
37
+ ```bash
38
+ scanfuse scan.xml -o report
39
+ scanfuse nmap.xml nuclei.json zap.xml -o report
40
+ scanfuse scan.json -f trivy --format-out json -o report
41
+ scanfuse scan.xml --min-severity High --host 192.0.2.1 -o report
42
+ cat scan.json | scanfuse -o report
43
+ ```
44
+
45
+ `scanfuse` takes one or more files, or `-` for standard input. It finds the format by itself. Use `-f` to choose one: `nmap`, `nuclei`, `zap`, `burp`, `nessus`, `trivy`.
46
+
47
+ | Option | Meaning |
48
+ |---|---|
49
+ | `-o`, `--output` | Output directory. Default `report`. |
50
+ | `--format-out` | `markdown` (default), `json` or `csv`. `markdown` writes one file per finding, `index.md` and `report.html`. |
51
+ | `-s`, `--min-severity` | `Critical`, `High`, `Medium`, `Low` or `Info`. Case does not matter. |
52
+ | `--host` | Only findings on this host. Must match the IP or host name exactly, ignoring case. `*` and `?` are wildcards, for example `10.0.0.*`. |
53
+ | `-t`, `--template-dir` | Folder with your own `finding.md.j2` and `index.md.j2`. The folder must exist. Missing files fall back to the built-in templates. |
54
+ | `--replace` | Overwrite an existing report. See below. |
55
+ | `--csv-excel` | CSV with a byte order mark and `;` as separator, which German-language Excel expects. |
56
+ | `--version` | Print the version. |
57
+
58
+ ### Existing reports
59
+
60
+ `scanfuse` never overwrites files by accident. If the output directory already contains a file that the new report would write, it stops with exit code 2 and changes nothing. Add `--replace` to overwrite the report. With `--replace`, files of the earlier report that are no longer part of it are removed. Files that scanfuse did not write are left alone.
61
+
62
+ The report is built in a temporary folder first. If moving the files fails, scanfuse tries to restore the old report. If restoration also fails, the error gives the location of the retained backup. A process or system crash during replacement can still leave a mixed report.
63
+
64
+ ### Incomplete runs
65
+
66
+ If one input file cannot be read, scanfuse still writes the findings from the other files and exits with code 1. The report says so:
67
+
68
+ * `report.json`: `"complete": false` and the list of `errors`.
69
+ * `index.md` and `report.html`: an **INCOMPLETE** notice with the errors.
70
+ * `report.csv`: the column `Import Status` says `incomplete (...)` in every row.
71
+
72
+ An invalid `.scanfuse.yaml` counts as an error too. The report is then incomplete, because ignore rules were not applied.
73
+
74
+ ### Exit codes
75
+
76
+ | Code | Meaning |
77
+ |---|---|
78
+ | 0 | Complete. |
79
+ | 1 | Incomplete or failed: an input could not be read, `.scanfuse.yaml` is invalid, nothing could be written, or no input was given. |
80
+ | 2 | Invalid option, or the output files already exist (use `--replace`). |
81
+
82
+ ## Merging duplicates
83
+
84
+ Findings from several files are merged when they are the same finding:
85
+
86
+ * the titles are equal, ignoring case and surrounding spaces,
87
+ * they carry the same CVE or GHSA ids,
88
+ * they have the same port, and
89
+ * the same tool does not give them different tool ids (for example two Nessus plugin ids).
90
+
91
+ Titles that are only similar are not merged. A merged finding keeps all hosts and all evidence. Evidence shows which tool reported it. What each tool wrote (description, remediation, severity, CVSS score and vector, ids) is listed under **Reported by** in the Markdown and in `sources` in the JSON. The summary severity is the highest of all reports. The summary CVSS score is the highest score, together with its own vector.
92
+
93
+ ## Output
94
+
95
+ ### JSON
96
+
97
+ `report.json` is an object:
98
+
99
+ ```json
100
+ {
101
+ "schema_version": 2,
102
+ "complete": true,
103
+ "errors": [],
104
+ "findings": [ { "title": "...", "severity": "High", "identifiers": ["CVE-2024-0001"], "port": "443/tcp", "sources": [] } ]
105
+ }
106
+ ```
107
+
108
+ ### CSV
109
+
110
+ Columns: `Title`, `Severity`, `CVSS`, `Hosts`, `Source Tool`, `Description`, `Remediation`, `Evidence`, `CVSS Vector`, `Port`, `Identifiers`, `References`, `Other Reports`, `Import Status`. `Hosts` shows `ip (host name)` when a host name is known.
111
+
112
+ Cells that start with `=`, `+`, `-` or `@` get a leading `'` to reduce the risk of spreadsheet formulas. This also covers look-alike characters such as a full-width `=` and a leading no-break space. These cases were tested by reading the files back.
113
+
114
+ In Excel, import `--csv-excel` output with **Data > From Text/CSV** and use the semicolon delimiter. In a Windows Excel test, this preserved a multiline evidence field; the older Text Import Wizard split it across rows. Formula handling and LibreOffice remain unverified in those applications.
115
+
116
+ ### Scanner text in the report
117
+
118
+ Scanner output is untrusted. scanfuse treats it like this:
119
+
120
+ * Titles, host names, ids and tool names are shown as plain text. They cannot add links, headings or HTML.
121
+ * Descriptions and remediation text keep Markdown formatting, including links. Raw HTML is escaped, and the text cannot create a heading. A link in a scanner description is shown as the scanner wrote it, so check it before you click.
122
+ * Evidence is shown unchanged in a code block.
123
+ * The HTML page removes scripts, images, frames and `javascript:` links.
124
+ * XML with entity declarations is rejected.
125
+
126
+ ## Configuration: `.scanfuse.yaml`
127
+
128
+ `scanfuse` reads `.scanfuse.yaml` from the current directory.
129
+
130
+ ```yaml
131
+ ignore_titles:
132
+ - "Wappalyzer"
133
+ - "Header Missing"
134
+ ```
135
+
136
+ Findings whose title contains one of these texts (ignoring case) are left out. `ignore_titles` must be a list of strings. A single string is read as a list with one entry. Anything else, a file that is not a mapping, or a YAML syntax error is reported as an error and the rules are not applied.
137
+
138
+ ## Build a standalone program
139
+
140
+ ```bash
141
+ uv sync --extra dev
142
+ uv run python build.py
143
+ ```
144
+
145
+ The program is written to `dist/`. To check it against the test suite:
146
+
147
+ ```bash
148
+ SCANFUSE_EXE=dist/scanfuse uv run pytest tests/test_packaged_cli.py # Linux, macOS
149
+ $env:SCANFUSE_EXE = (Resolve-Path .\dist\scanfuse.exe).Path # Windows PowerShell
150
+ ```
151
+
152
+ A program built on Linux depends on the C library of the system it was built on. Build on the oldest system you want to support.
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ uv sync --locked --extra dev
158
+ uv run ruff check .
159
+ uv run mypy scanfuse
160
+ uv run pytest tests/
161
+ ```
162
+
163
+ ## Limits
164
+
165
+ * scanfuse was tested with real output from nmap (7.94, 7.12, 6.40), ZAP 1.4, Burp, Nessus and Trivy (three file layouts), taken from the public [DefectDojo sample-scan-files](https://github.com/DefectDojo/sample-scan-files). Those files are old. Newer versions of the tools may add fields that scanfuse does not read yet. Nuclei has no real sample file in the tests. Open an issue with a cleaned sample if a file fails.
166
+ * Format detection looks at the start of the file. Use `-f` if it guesses wrong.
167
+ * The HTML page and the CSV file were checked by reading the files. They were not opened by hand in a browser, Excel or LibreOffice.
168
+ * 100,000 results were processed completely in all three output formats on one Linux machine (4 CPUs, 16 GB). JSON took 14 seconds, CSV 10 seconds, Markdown with HTML 124 seconds, and the peak memory was between 0.7 and 1.0 GB. The HTML page for that many findings is large. You can repeat the check with `python tools/large_check.py 100000`.
169
+ * Standalone programs are built and tested in CI on Ubuntu 22.04 (x86_64), Windows (x86_64) and macOS (arm64). Other Linux distributions, Linux on ARM and macOS on Intel were not tested.
170
+
171
+ ## License
172
+
173
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,23 @@
1
+ # Security
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please report vulnerabilities privately and do not open a public issue. Use GitHub's private vulnerability reporting for this repository (Security tab, "Report a vulnerability").
6
+
7
+ Supported: the latest release.
8
+
9
+ ## What scanfuse handles
10
+
11
+ scanfuse reads scanner files that may come from untrusted targets (titles, banners and evidence are attacker-controlled text) and writes Markdown, HTML, JSON and CSV.
12
+
13
+ * XML with entity declarations is rejected.
14
+ * Titles, host names and ids are escaped in Markdown and HTML. The HTML page removes scripts, images, frames and `javascript:` links.
15
+ * CSV cells that start with `=`, `+`, `-`, `@` or look-alike characters are prefixed with `'`.
16
+ * Nothing is sent over the network.
17
+
18
+ ## Known limits
19
+
20
+ * Descriptions and remediation text keep Markdown, including links. Links are shown as the scanner wrote them.
21
+ * The HTML page and CSV output were checked by reading the files, not by opening them in a browser, Excel or LibreOffice.
22
+ * Format detection looks at the start of the file.
23
+ * Release programs are not signed. Releases come with a `SHA256SUMS` file.
@@ -0,0 +1,57 @@
1
+ # Security Model
2
+
3
+ `scanfuse` is an offline command-line utility that imports pentest scanner outputs (Nmap, Nuclei, OWASP ZAP, Burp Suite, Nessus, and Trivy), deduplicates findings, and exports unified reports as Markdown, standalone HTML, JSON, or CSV.
4
+
5
+ ## Deployment Context
6
+
7
+ * Runs as a local single-user CLI process (`scanfuse/cli.py`).
8
+ * Performs local file I/O only. It opens no network sockets and makes no outbound HTTP or DNS requests.
9
+ * Processes scanner output files that frequently contain attacker-controlled strings captured from scanned targets (HTTP banners, headers, responses, TLS subject names, and reflected payloads).
10
+
11
+ ## Trust Boundaries and Inputs
12
+
13
+ ### Untrusted Inputs
14
+
15
+ * **Scanner input files** (`file_path` arguments and `stdin` in `scanfuse/cli.py`, parsed by `scanfuse/parsers/*.py`):
16
+ * XML inputs (`nmap.py`, `zap.py`, `burp.py`, `nessus.py`) and JSON/JSONL inputs (`nuclei.py`, `trivy.py`) are untrusted. Both the file structure and all embedded text fields (`title`, `description`, `remediation`, `host`, `evidence`, `references`, `identifiers`) may be attacker-controlled.
17
+
18
+ ### Trusted Operator Inputs
19
+
20
+ * **Command-line arguments** (`--output`, `--format`, `--format-out`, `--min-severity`, `--host`, `--template-dir`, `--replace`, `--csv-excel`).
21
+ * **Local configuration file** (`.scanfuse.yaml` in the current working directory, loaded via `yaml.safe_load` in `scanfuse/cli.py`).
22
+ * **Custom Jinja2 templates** (`--template-dir`, loaded by `ScanfuseEngine._template_env` in `scanfuse/engine.py`).
23
+
24
+ ## Security Invariants
25
+
26
+ 1. **No XML External Entities (XXE) or DTD Expansion:**
27
+ All XML parsing in `scanfuse/parsers/nmap.py`, `zap.py`, `burp.py`, and `nessus.py` must use `defusedxml.ElementTree.iterparse` and reject entity or DTD declarations (`DefusedXmlException` caught in `ScanfuseEngine.process_file`).
28
+ 2. **HTML Sanitization in Standalone Reports:**
29
+ All Markdown fragments rendered into `report.html` must pass through `nh3.clean` in `scanfuse/html_report.py` (`render_report_fragment`), restricting elements to `REPORT_TAGS` (`a`, `b`, `blockquote`, `br`, `code`, `del`, `div`, `em`, `h1`–`h6`, `hr`, `i`, `li`, `ol`, `p`, `pre`, `s`, `strong`, `table`, `tbody`, `td`, `th`, `thead`, `tr`, `ul`) and URL schemes on `a[href]` to `http`, `https`, and `mailto`.
30
+ 3. **Markdown Escaping and Fence Isolation:**
31
+ * Scalar fields (`title`, `ip`, `hostname`, `os`, `source_tool`, `port`, `identifiers`) use the `md` filter (`md_escape` in `scanfuse/engine.py`) so they cannot inject HTML tags, Markdown links, or headings.
32
+ * Free-text fields (`description`, `remediation`) use the `mdtext` filter (`md_text` in `scanfuse/engine.py`), which HTML-escapes raw `<`/`>`/`&` and escapes leading `#`/`=`/`-` heading markers.
33
+ * Raw evidence blocks use the `fence` filter (`code_fence` in `scanfuse/engine.py`), which computes a backtick fence strictly longer than any backtick run inside the evidence payload.
34
+ 4. **Spreadsheet Formula Neutralization (CSV Injection):**
35
+ Every cell written by `ScanfuseEngine.export_csv` in `scanfuse/engine.py` must pass through `csv_safe`, which normalizes text with `NFKC` and prepends a single quote (`'`) whenever the normalized cell starts with `=`, `+`, `-`, or `@`, or the raw cell starts with whitespace (`\t`, `\r`, `\n`, space).
36
+ 5. **Safe Output File Names and Staged Publishing:**
37
+ * Per-finding Markdown file names are generated by `unique_slug` (`scanfuse/utils.py`), which restricts characters to `[a-z0-9-]`, caps length at 100 characters, and blocks `RESERVED_SLUGS` (`index`, `report`, Windows device names `con`, `prn`, `aux`, `nul`, `com1`–`com9`, `lpt1`–`lpt9`).
38
+ * `ScanfuseEngine._publish` writes into a temporary staging directory first, refuses to overwrite existing files without `--replace`, and ignores dot-files or path segments when cleaning up stale files from `.scanfuse-manifest-<kind>.json`.
39
+
40
+ ## In-Scope Vulnerability Classes
41
+
42
+ * **XXE or XML entity expansion** bypassing `defusedxml` in `scanfuse/parsers/*.py`.
43
+ * **Cross-Site Scripting (XSS)** in generated `report.html` via crafted scanner fields bypassing `md_escape`, `md_text`, `code_fence`, or `nh3.clean` (`scanfuse/engine.py`, `scanfuse/html_report.py`).
44
+ * **Path traversal or arbitrary file overwrite** outside `--output` triggered by crafted finding titles, slugs, or manifest entries (`scanfuse/utils.py`, `scanfuse/engine.py`).
45
+ * **CSV formula injection** bypassing `csv_safe` (`scanfuse/engine.py`).
46
+ * **Unsafe deserialization** in configuration or parser loading (e.g., replacing `yaml.safe_load` in `scanfuse/cli.py`).
47
+
48
+ ## Out of Scope / Non-Issues
49
+
50
+ * **Jinja2 `autoescape=False` in `ScanfuseEngine._template_env` (`scanfuse/engine.py`):**
51
+ Jinja2 renders Markdown templates (`finding.md.j2`, `index.md.j2`), not HTML directly. Escaping is enforced explicitly via the `| md`, `| mdtext`, and `| fence` template filters, and the resulting HTML is sanitized by `nh3.clean` in `scanfuse/html_report.py`.
52
+ * **Malicious custom templates passed via `--template-dir` or local `.scanfuse.yaml`:**
53
+ The CLI user running `scanfuse` and choosing `--template-dir` or `.scanfuse.yaml` already executes with their own local user privileges.
54
+ * **Scanner URLs in `references` or `description` pointing to external phishing/malicious sites:**
55
+ Preserving `http://` and `https://` links reported by scanners is intended behavior (documented in `SECURITY.md`); only non-web schemes such as `javascript:` or `data:` are in scope.
56
+ * **Memory exhaustion on multi-gigabyte single-line inputs:**
57
+ Local resource consumption from oversized local files passed by the operator is a known operational limit, not a privilege boundary violation.
@@ -0,0 +1,23 @@
1
+ import argparse
2
+ from pathlib import Path
3
+
4
+ import PyInstaller.__main__
5
+
6
+
7
+ def build(output_dir: str = "dist", work_dir: str = "build"):
8
+ project_dir = Path(__file__).parent.resolve()
9
+ PyInstaller.__main__.run([
10
+ str(project_dir / "scanfuse.spec"),
11
+ '--clean',
12
+ '--noconfirm',
13
+ f'--distpath={project_dir / output_dir}',
14
+ f'--workpath={project_dir / work_dir}',
15
+ '--log-level=WARN',
16
+ ])
17
+
18
+ if __name__ == "__main__":
19
+ parser = argparse.ArgumentParser(description="Build the standalone scanfuse executable.")
20
+ parser.add_argument("--output-dir", default="dist")
21
+ parser.add_argument("--work-dir", default="build")
22
+ args = parser.parse_args()
23
+ build(args.output_dir, args.work_dir)