feat(webui): add a local web UI for the scan pipeline - #235
Merged
Conversation
gadievron
requested review from
dgeyshis,
shahar-davidson and
sounil
as code owners
August 14, 2026 18:53
Adds an opt-in, loopback-only web UI (`openant serve`) that drives the existing scan pipeline from a browser: submit a repo URL or local path, watch scan logs stream over SSE, and read the HTML report / markdown summary / disclosures. Original work by Sounil Yu (PR #25, branch knostic/webui-2); this reconciles it onto current master (taking master on the 5 superseded Python-side conflicts, keeping only the additive Go/UI surface + a one-line serveCmd registration) and hardens it. Supersedes #25. Security posture — the server binds a loopback address (127.0.0.1 by default), EVERY route rejects a non-loopback Host (DNS-rebinding guard in the securityHeaders middleware, GET routes included), and every state-changing request additionally requires a per-instance crypto/rand CSRF token (constant-time compare) plus a same-origin check. So the read and write surfaces are both reachable only by a caller already on the local origin — this is defense-in-depth hardening, not a remotely-exploitable vulnerability. One residual caveat, tracked below: the live HTML report loads Tailwind/Chart.js from CDNs, so a CDN compromise could run script in the server origin. - git-clone SSRF guard (repoHostBlocked): classifies canonical IPs by value (loopback/link-local/unspecified/AWS IPv4+IPv6 IMDS/Alibaba blocked; RFC1918 + IPv6-ULA allowed for internal servers), resolves DNS names and blocks any sensitive result, and blocks ANY non-canonical numeric literal outright (encoding-proof for literals: decimal/octal/hex/short-form/mod-2^32-wrap, IPv6 zone-ids, IPv4-mapped forms). Fails closed on non-ASCII/IDNA hosts, backslash/ unparseable URLs, and scp multi-userinfo; blocks the .localhost TLD; passes git -c http.followRedirects=false. DNS-rebinding of a repo hostname between the check and the clone is out of scope (the guard covers literals + one resolution). - Rejects credentials embedded in an http(s) repo URL (they would be logged verbatim); the python subprocess runs with -P so a hostile openant/ package in the scanned repo can't shadow the real module. - DOMPurify 3.4.13 (vendored, no CDN; updated from 3.1.6 = CVE-2025-15599 / CVE-2026-41238) with a strict INERT allowlist (no style/form/img/svg — a repo can break out of a markdown code fence, so the default allowlist's active content is refused) on client-rendered markdown + a strict bluemonday allowlist on the report SafeRemediation path; the API-key value is never sent to any page. - Robustness: managed-writer stderr with WaitDelay (no deadlock on a detached child; a WaitDelay-tripped exit-0 scan keeps its output rather than erroring), signal-exit treated as error, LogBuf bounded by line count AND bytes, delete that waits (up to 5s) for the runner before removing the dir, and a synchronized shutdown (drainMu + WaitShutdown) that cancels in-flight scans before exit. - The job output dir holds files derived from the untrusted repo, so served files (report.html, summary, disclosures) are opened O_NOFOLLOW and confirmed to be regular files within the job dir (a symlink to a host secret is refused atomically at open — no check-then-read race), and the clone is time-bounded (15m) so a hostile remote can't hold a scan slot forever. Tested: host-classification suite (41 blocked forms + a legit allow-set), auth-gate rejections (rebinding Host, cross-site, foreign Origin, credential URL), remediation-XSS, job-lifecycle (drain/cancel/recover/delete-waits-for-runner), the SSE Last-Event-ID overflow clamp, and the stderr WaitDelay mechanism. go build / go vet / go test ./... green; go test -race clean; semgrep + CodeQL (CWE-918 SSRF = 0); real end-to-end scans of Python/Go/JS repos through the UI. Deferred (tracked, not in this PR): vendoring the report-template CDN scripts (Tailwind is a CDN JIT; needs an asset build step, and the template is shared with `openant report -f html`) and the CSP/COEP that depends on it; the marked 12.0.2 update (no known CVE; output is DOMPurify-sanitized); the unbounded stdout capture buffer (openant's stdout is its own bounded JSON envelope). Process-group kill (Setpgid/SIGKILL) and O_NOFOLLOW are Unix-only, so they live in procgroup_unix.go / procgroup_other.go build-tagged files; the code builds and vets clean on linux, darwin, and windows. Reproduce: git fetch origin && git checkout <this-branch> cd apps/openant-cli && go build -o /tmp/openant-cli . && /tmp/openant-cli serve --addr 127.0.0.1:8765 go test ./... Co-authored-by: Sounil Yu <4305467+sounil@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gadievron
added a commit
that referenced
this pull request
Aug 14, 2026
The web UI (openant serve) landed in the code (#235) but its doc updates missed that merge. Add them: a "Web UI" section in the README, a CHANGELOG entry, and `serve` in the ARCHITECTURE command list. Docs-only; no code change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(webui): add a local web UI for the scan pipeline
Adds an opt-in, loopback-only web UI (
openant serve) that drives the existingscan pipeline from a browser: submit a repo URL or local path, watch scan logs
stream over SSE, and read the HTML report / markdown summary / disclosures.
Original work by Sounil Yu (PR #25, branch knostic/webui-2); this reconciles it
onto current master (taking master on the 5 superseded Python-side conflicts,
keeping only the additive Go/UI surface + a one-line serveCmd registration) and
hardens it. Supersedes #25.
Security posture — the server binds a loopback address (127.0.0.1 by default), EVERY route rejects a
non-loopback Host (DNS-rebinding guard in the securityHeaders middleware, GET
routes included), and every state-changing request additionally requires a
per-instance crypto/rand CSRF token (constant-time compare) plus a same-origin
check. So the read and write surfaces are both reachable only by a caller
already on the local origin — this is defense-in-depth hardening, not a
remotely-exploitable vulnerability. One residual caveat, tracked below: the live
HTML report loads Tailwind/Chart.js from CDNs, so a CDN compromise could run
script in the server origin.
(loopback/link-local/unspecified/AWS IPv4+IPv6 IMDS/Alibaba blocked; RFC1918 +
IPv6-ULA allowed for internal servers), resolves DNS names and blocks any
sensitive result, and blocks ANY non-canonical numeric literal outright
(encoding-proof for literals: decimal/octal/hex/short-form/mod-2^32-wrap, IPv6
zone-ids, IPv4-mapped forms). Fails closed on non-ASCII/IDNA hosts, backslash/
unparseable URLs, and scp multi-userinfo; blocks the .localhost TLD; passes
git -c http.followRedirects=false. DNS-rebinding of a repo hostname between the
check and the clone is out of scope (the guard covers literals + one resolution).
verbatim); the python subprocess runs with -P so a hostile openant/ package in
the scanned repo can't shadow the real module.
CVE-2026-41238) with a strict INERT allowlist (no style/form/img/svg — a repo
can break out of a markdown code fence, so the default allowlist's active
content is refused) on client-rendered markdown + a strict bluemonday allowlist on
the report SafeRemediation path; the API-key value is never sent to any page.
child; a WaitDelay-tripped exit-0 scan keeps its output rather than erroring),
signal-exit treated as error, LogBuf bounded by line count AND bytes, delete
that waits (up to 5s) for the runner before removing the dir, and a synchronized
shutdown (drainMu + WaitShutdown) that cancels in-flight scans before exit.
files (report.html, summary, disclosures) are opened O_NOFOLLOW and confirmed to
be regular files within the job dir (a symlink to a host secret is refused
atomically at open — no check-then-read race), and the clone
is time-bounded (15m) so a hostile remote can't hold a scan slot forever.
Tested: host-classification suite (41 blocked forms + a legit allow-set),
auth-gate rejections (rebinding Host, cross-site, foreign Origin, credential URL),
remediation-XSS, job-lifecycle (drain/cancel/recover/delete-waits-for-runner),
the SSE Last-Event-ID overflow clamp, and the stderr WaitDelay mechanism. go
build / go vet / go test ./... green; go test -race clean; semgrep + CodeQL
(CWE-918 SSRF = 0); real end-to-end scans of Python/Go/JS repos through the UI.
Deferred (tracked, not in this PR): vendoring the report-template CDN scripts
(Tailwind is a CDN JIT; needs an asset build step, and the template is shared
with
openant report -f html) and the CSP/COEP that depends on it; the marked12.0.2 update (no known CVE; output is DOMPurify-sanitized); the unbounded stdout
capture buffer (openant's stdout is its own bounded JSON envelope).
Reproduce:
git fetch origin && git checkout
cd apps/openant-cli && go build -o /tmp/openant-cli . && /tmp/openant-cli serve --addr 127.0.0.1:8765
go test ./...
Coordination
Co-authored-bypreserved). feat(webui): add web user interface #25 can be closed in favor of this.cmd/root.gois also touched by feat: add generate-context CLI command with auto-discovery #41 and feat: override merge mode for generate-context #47 (both DRAFT, generate-context subcommands): complementaryrootCmd.AddCommand(...)additions, not conflicting — coordinate ordering only if either leaves draft.Known limitations (documented, not addressed here)
openant report -f html, rendered same-origin at/report/{id}); vendoring needs an asset build step. Full CSP/COEP depend on it.--dynamic-testexposes the existing core dynamic-tester (unchanged by this PR).Reviewed via an independent multi-lens security pass (SSRF, CSRF, DNS-rebinding, XSS, markdown-injection, filesystem/symlink, DoS) with all in-scope findings fixed.