Skip to content

argus view browser — local read-only web UI

After a scan produces argus-results.json, there are three ways to look at it today: open the JSON directly (for engineers comfortable with the shape), run argus view terminal (for engineers who live in the terminal), or argus view browser — a tiny web app bundled with the SDK that serves the same findings to anyone with a browser.

Aimed at owners, managers, and executives who want at-a-glance insight into their product's security posture without digging through CI logs or learning a TUI.

Install

pip install 'argus-security[browser]'

Pulls in FastAPI, uvicorn, Jinja2, and python-multipart (~10 MB total). Without the extra, running argus view browser prints a friendly install hint and exits cleanly — the extra is never required for argus scan or any other subcommand.

Launch

argus view browser                          # picker rooted at CWD
argus view browser /path/to/results/        # load that scan directly
argus view browser /path/to/scans-parent/   # picker rooted there
argus view browser --port 9090              # non-default port
argus view browser --no-open                # skip auto-opening the browser (default opens it on TTY)

Localhost only. The server always binds to 127.0.0.1 — there is no --bind flag by design. Single-user, single-machine is the product shape; it's what makes "no auth, no sessions, no CSRF handling" the right scope. For multi-user enterprise deployments, see the separate argus-portal track.

Views

/ — Executive summary dashboard

Opens here by default. Shows:

  • Total findings + per-severity breakdown (cards at the top)
  • Scan quality warnings — SPDX-2.1 SBOMs Trivy can't read, low-purl coverage, Grype "couldn't identify scan subject" — surfaced loudly so empty scans aren't misread as clean
  • Per product — every SBOM source with total + critical + high counts and the top-3 findings (severity, ID, package, title)
  • Per scanner — contribution counts (useful for spotting when one scanner did 90% of the work, which often signals an input format issue)

/findings — Filterable table

The spreadsheet view. Dropdown filters for severity, product, and scanner; a search box that matches id, title, location, CVE, and scanner name. Filters combine with AND semantics — the exact same logic the TUI uses.

URLs are bookmarkable:

http://localhost:8080/findings?scan=/path/to/results&min_severity=high&product=BVMS.spdx

JS-enhanced sessions get live filter updates without a page reload; non-JS clients see the same content via a plain form submit (Apply button is the fallback).

/picker — Switch scan

A one-level file browser. Click into subdirectories; any directory containing an argus-results.json gets a "Load scan" button with a finding-count peek. Dotfiles and common build directories (node_modules, .git, .venv, etc.) are hidden by default; ?show_hidden=1 reveals them.

Deliberately non-recursive — users drive the navigation. That keeps the picker fast (no filesystem walk on every request) and avoids decisions about depth limits and gitignore semantics.

/healthz

Liveness check. Returns {"status": "ok", "root": "<picker-root>"}. Handy for scripts that need to know when the server has started up.

Security posture

  • CSP header on every response: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'. Blocks inline scripts and any cross-origin resources.
  • X-Frame-Options: DENY — the dashboard can't be framed into a parent page (harmless in the localhost-only context, defensive against future non-localhost deployments).
  • Jinja2 autoescape — scanner-supplied text (finding titles, descriptions) is HTML-escaped before reaching the page. Combined with the CSP, an attacker-controlled CVE description can't execute script in the browser.
  • No auth, no cookies, no sessions — localhost-only means single user; there's nothing for a session to represent.
  • No mutations — every route is GET. Nothing on the server to defend against CSRF.
  • Input sanitization — filter values passed via URL are validated; bogus min_severity=?? falls back to "no filter" instead of crashing the route.

Not in v1 (tracked as future roadmap items)

  • Live reload when argus-results.json changes on disk. Right now: browser refresh to re-read the file.
  • Scan-over-scan diff — compare today's scan to last week's.
  • JSON API endpoints — external tools (Slack bots, custom dashboards) can read argus-results.json directly today; we'll add /api/... if a real use case shows up.
  • Recursive picker walks — one directory level at a time is the current design. If users ask for "find all scans under this tree," that's a separate UX decision (performance, gitignore semantics, skip-rules).

Architecture relationship to argus view terminal and argus-portal

Three UIs look at the same data; each has its own scope:

Tool Scope Audience
argus view terminal (TUI) Single scan, read-only Engineer triaging in a terminal
argus view browser (this) Single scan, read-only, local web Owner / manager / exec on the same machine
argus-portal Multi-scan, multi-user, persistent DB, OAuth, FedRAMP Enterprise compliance organization

argus view terminal and argus view browser share a moduleargus.core.findings_view — that owns the filter / sort / summary logic. ViewState semantics, compute_summary output, and severity ordering are identical across the TUI and the web view. A filter that produces 17 results in the TUI produces the same 17 in the browser.

argus-portal is a separate project on a separate track. It doesn't share code today; when it matures, we may factor a third consumer onto findings_view so all three agree on what "high severity" means and which findings match which filter.

Troubleshooting

argus: error: argument command: invalid choice: 'serve' — your argus binary was installed before serve landed. Reinstall: pip install --upgrade --pre 'argus-security[browser]'.

"The local web UI needs the 'serve' extra" — you installed argus-security but not the [serve] extra. Retry with pip install 'argus-security[browser]'.

Port 8080 already in use — pass --port 9090 (or any free port).

Browser didn't auto-open — we use the stdlib webbrowser module which is generally reliable but can fail silently in headless or SSH-remoted environments. The URL is printed to stdout — open it manually.

Page renders "No scan loaded" — the directory you pointed argus view browser at doesn't contain an argus-results.json. Use /picker to navigate to one, or pass ?scan=/abs/path on /.