Skip to content

feat(audit): render a report you can send to whoever runs the router - #6

Merged
gclluch merged 1 commit into
mainfrom
feat/html-report
Aug 1, 2026
Merged

feat(audit): render a report you can send to whoever runs the router#6
gclluch merged 1 commit into
mainfrom
feat/html-report

Conversation

@gclluch

@gclluch gclluch commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Phase 2b. The terminal report answers "what is wrong here" for the person who ran it. This answers it for the person they forward it to - a housemate, a landlord, whoever actually administers the router. That reader will not run the tool, so the file has to carry the whole lesson with it.

netdiff audit 192.168.1.0/24 --html report.html writes one self-contained file. No server, no assets, no network, no JavaScript. Progressive disclosure is native <details>/<summary> - the same ladder as the terminal, headline collapsed and the lesson one click away. Scripted show/hide would be a dependency, a bug surface, and a thing that breaks when the file is emailed through something that strips scripts.

It is audit --html, not a standalone report command. store.record_findings deliberately persists only rule/severity/device/title/evidence - the lesson text lives in audit.RULES and is formatted at render time with context ({port}, {external_port}, {control_url}) that was never stored. A report built from history could therefore only show the lesson with its placeholders blanked. Rendering from the audit you just ran gets full fidelity for a fraction of the code, and leaves the "improve an explanation and it improves everywhere" property intact.

Escaping

This is the second place in the codebase where untrusted strings become a language other than Python. The first was the verify commands, where an unquoted value became shell - fixed in #3. Device names, banners, mDNS labels and the router's own description of a port forward all reach this page, so every interpolation goes through esc() with quote=True, and tests/test_report.py puts a hostile value through each of the five finding fields.

severity is the only value landing inside an attribute, and it is safe because it comes from the rule table rather than the network. There is a test pinning that set closed, so if a rule ever grows a free-form severity the quoting becomes load-bearing and this fails first.

@gclluch
gclluch merged commit 02361e1 into main Aug 1, 2026
8 checks passed
@gclluch
gclluch deleted the feat/html-report branch August 1, 2026 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant