An in-browser, VS Code–style viewer for OpenTaint taint-analysis results. Point it at any project's SARIF + sources + rules and get a self-contained, offline HTML report — click through findings, trace each tainted flow from source to sink, and read the rules that fired, all without a server.
A bundled demo built from
seqra/java-spring-demo is committed
so you can try the viewer instantly.
The viewer is a generic React app that renders one committed data/content.json.
To produce a self-contained, offline HTML report for your own project, run three
commands.
Install the CLI — it bundles the ruleset alongside the binary at
<install-prefix>/lib/rules, so a native install gives you both the engine and
the rules in one step:
# Linux / macOS
curl -fsSL https://opentaint.org/install.sh | bash
# macOS (Homebrew)
brew install --cask seqra/tap/opentaint
# Windows (PowerShell)
irm https://opentaint.org/install.ps1 | iexScan your project — writes a SARIF report next to it:
opentaint scan --output results.sarif your-projectPrefer not to install the CLI? Run the engine through Docker instead:
docker run --rm \ -v "$PWD/your-project:/project" \ ghcr.io/seqra/opentaint \ opentaint scan --output /project/results.sarif /projectDocker users also need to extract the ruleset once (the native install already includes it):
mkdir -p rules docker run --rm --entrypoint sh \ -v "$PWD/rules:/out" \ ghcr.io/seqra/opentaint \ -c 'cp -r /usr/local/lib/opentaint/lib/rules/. /out/'Pin the engine by digest (
ghcr.io/seqra/opentaint@sha256:…) for reproducible reports. See the OpenTaint quick-start for the canonical invocation and the digest of the version you intend to use.
Point --rules at the bundled ruleset that came with the install (or at the
rules/ directory you extracted in the Docker fallback above):
npm install # once
npm run gen -- \
--sarif your-project/results.sarif \
--src your-project/src \
--rules "$(dirname "$(command -v opentaint)")/../lib/rules" \
--name your-projectThat writes data/content.json: the findings, the source files they reference
(pruned to only those), the full ruleset, and the analyzer name + version read
from the SARIF (shown in the TopBar, e.g. v0.3.0 · 2026.05.15.f15ed3a).
If your SARIF artifactLocation.uri values aren't relative to the parent of
--src (the common <root>/src/... layout), pass --root <dir> so collected
file paths match the URIs.
npm run build:singledist-single/index.html is a single self-contained file — JS, CSS, fonts, and
Monaco editor all inlined. Open it in a browser or share it as-is; no server,
no network required.
For a hosted (multi-file) build instead, use npm run build → dist/.
The repo commits a data/content.json built from seqra/java-spring-demo, so
you can see the viewer in action before pointing it at your own project:
npm install
npm run dev # start the Vite dev serverThen open the URL Vite prints (default http://localhost:5173). The demo covers 13 findings — Template Injection, SSRF (Kotlin), and XSS — over the source files they reference and 47 rules.
- Findings & Rules trees — a VS Code activity bar switches the left sidebar between the findings tree (grouped by directory, with severity dots) and the ruleset tree (built-in vs. custom rules).
- Monaco code editor with taint-path decorations: the flow is highlighted in blue, the sink in red. Jump step-by-step through the path; cross-file hops switch the active file automatically.
- Finding info panel — rule description (markdown), CWE tags, severity, and the ordered list of taint steps (source → propagation → sanitizer → sink).
- Flexible layout — toggle the editor and info panel between tabbed and side-by-side split views; resizable panels throughout.
- Light/dark theme with brand-matched Monaco themes (
ot-light/ot-dark) and JetBrains Mono. - View persistence — your selected finding, step, files, and layout are saved to
localStorageand restored on refresh. - Offline single-file export — build a single self-contained
index.html(JS, CSS, fonts, and Monaco all inlined).
| Script | What it does |
|---|---|
npm run gen |
Generate data/content.json from a SARIF + source dir + rules dir. |
npm run build:single |
Build a single self-contained offline index.html into dist-single/. |
npm run build |
Type-check (tsc --noEmit) and build the hosted site to dist/. |
npm run dev |
Vite dev server with HMR. |
npm run preview |
Serve the production build locally. |
npm test |
Run the unit/component test suite (Vitest) once. |
npm run test:watch |
Vitest in watch mode. |
npm run coverage |
Vitest with V8 coverage. |
npm run e2e |
Playwright end-to-end tests. |
The viewer is fully static — no backend, no network calls for analysis. It loads a single bundled content file and renders everything from it:
data/content.json ← committed, pre-analyzed content
│
▼
loadContent() → validate (isViewerContent) → Zustand store → UI
The content shape is defined in src/types/content.ts:
tool— the analyzer name and version (semver + build) read from the SARIF.findings— each with a rule id, vuln class, severity, CWE tags, markdown description, primary location, code flows, and orderedTaintSteps.files— the project source files referenced by findings.rules— the ruleset (builtinorcustom), keyed by their real ruleset-relative path so findings can link straight to the rule that defined them.
State lives in a single Zustand store. The persisted slice (the
view — selected finding/step/files and layout, not the bundled content) is written to
localStorage under ot-view and validated on rehydrate so stale or corrupt storage
can't render an invalid view.
src/
components/ UI: AppShell, TopBar, ActivityBar, trees, EditorArea, InfoPanel, ...
content/ bundled content + loader/validation
pipeline/ SARIF → content transform (sarif.ts)
rules/ rule line/ref helpers
state/ Zustand store + theme
taint/ taint-path decorations + step navigation
types/ content type model + guard
util/ path, tree, severity, file-tab helpers
scripts/
gen-content.ts generates data/content.json from a SARIF + source + rules
e2e/ Playwright specs
fixtures/ sample SARIF for tests
- Unit/component: Vitest + Testing Library + jsdom
(
npm test, coverage vianpm run coverage). Tests live next to the code they cover. - End-to-end: Playwright (
npm run e2e). The smoke test derives its expectations from the committed content so it survives agenrun.
CI (.github/workflows/ci.yml) runs the build, coverage, and Playwright suite on every
push to main and on pull requests.
React 18 · TypeScript · Vite · Monaco Editor · Zustand · react-resizable-panels · Lucide icons · JetBrains Mono.
- OpenTaint: https://opentaint.org/
- Engine & CLI: https://github.com/seqra/opentaint
- Demo project analyzed here: https://github.com/seqra/java-spring-demo