miru is a terminal file viewer distributed as a static Go binary. It runs entirely on the user's machine; see PRIVACY.md for the full network egress policy.
The latest published release is supported. miru follows semantic versioning on the 0.x track; older 0.x.y releases receive no backports. Users on unsupported versions should upgrade before reporting an issue.
| Version | Supported |
|---|---|
Latest 0.x.y (see Releases) |
Yes |
Older 0.x.y |
No |
Do not open a public issue for security reports. miru uses GitHub's private vulnerability reporting channel exclusively.
To report a vulnerability:
- Go to https://github.com/hir4ta/miru/security/advisories/new.
- Fill out the advisory form with:
- A clear summary of the issue (one sentence).
- The affected component (
cmd/miru,internal/installer,internal/render,internal/tui,install.sh, release pipeline). - A minimal reproduction (file or input, exact command, observed vs. expected behavior).
- Your assessment of severity and impact.
- The maintainer will acknowledge within 7 days and provide a triage decision within 14 days.
If you cannot use GitHub advisories for some reason, email [email protected] with the same content. Do not post details on social media or public forums before a fix is published.
From v0.7.0 onward, every release artifact is signed and attested:
checksums.txtis signed with a Sigstore keyless cosign signature. The certificate identity is the workflow that produced the release (https://github.com/hir4ta/miru/.github/workflows/release.yml@refs/tags/<tag>), and the issuer is GitHub's OIDC provider.- Each tarball carries an SLSA build provenance attestation published to the GitHub attestation store and the Sigstore transparency log.
End-to-end verification (use cosign from https://docs.sigstore.dev/cosign/installation/ and gh from https://cli.github.com/). The flow below targets releases that ship a Sigstore bundle (.sigstore.json); for legacy releases (v0.7.0–v0.7.4) jump to Legacy releases.
# Resolve the latest release tag (or set TAG manually to a specific release).
TAG=$(curl -fsSI https://github.com/hir4ta/miru/releases/latest | awk 'tolower($1) == "location:" {print $2}' | awk -F/ '{print $NF}' | tr -d '\r')
ASSET=miru_${TAG#v}_$(uname | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz
URL=https://github.com/hir4ta/miru/releases/download/${TAG}
curl -fsSL "${URL}/${ASSET}" -o "${ASSET}"
curl -fsSL "${URL}/checksums.txt" -o checksums.txt
curl -fsSL "${URL}/checksums.txt.sigstore.json" -o checksums.txt.sigstore.json
# 1. Cosign signature on checksums.txt
cosign verify-blob \
--bundle checksums.txt.sigstore.json \
--certificate-identity-regexp "https://github.com/hir4ta/miru/.+" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txt
# 2. Tarball SHA-256 matches checksums.txt
shasum -a 256 -c checksums.txt --ignore-missing
# 3. SLSA build provenance attestation
gh attestation verify "${ASSET}" --repo hir4ta/miruAny failure of step 1 or 3 means the artifact did not come from this repository's release pipeline — do not run it.
These releases predate the cosign v3 bundle migration; they ship separate .sig/.pem files instead of a .sigstore.json bundle. Substitute the download and verification steps with:
TAG=v0.7.4 # or v0.7.0, v0.7.1, v0.7.2, v0.7.3
ASSET=miru_${TAG#v}_$(uname | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz
URL=https://github.com/hir4ta/miru/releases/download/${TAG}
curl -fsSL "${URL}/${ASSET}" -o "${ASSET}"
curl -fsSL "${URL}/checksums.txt" -o checksums.txt
curl -fsSL "${URL}/checksums.txt.sig" -o checksums.txt.sig
curl -fsSL "${URL}/checksums.txt.pem" -o checksums.txt.pem
cosign verify-blob \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
--certificate-identity-regexp "https://github.com/hir4ta/miru/.+" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txtSteps 2 (shasum -a 256 -c) and 3 (gh attestation verify) are unchanged.
miru's primary attack surfaces:
- Release distribution.
curl | sh,miru update, andbrew installall fetch prebuilt binaries from GitHub Releases. Both the shell bootstrap andmiru updateverify the SHA-256 of the downloaded tarball against the release'schecksums.txtbefore extraction. A compromise of the release pipeline (workflow secrets, runner) would still bypass this gate; the workflow uses pinned action versions, least-privilege permissions, and a fine-grained PAT scoped only to the Homebrew tap repository. - Browser preview (
bkey). Pressingbstarts a loopback HTTP server (127.0.0.1:<random>, lives only while the TUI is open) and opens the rendered page in the default browser. The server is sandboxed to the file's project root (the first ancestor with.git,go.mod,package.json,Cargo.toml, orpyproject.toml; otherwise the file's own directory), enforces the sandbox both lexically and afterEvalSymlinksso symlinks cannot escape, and rejects any request outside the root with 403 before touching disk. The HTML page rendered for Markdown enables goldmark'sunsafemode (raw HTML pass-through) and runs mermaid withsecurityLevel: "loose"— matching the behavior of Obsidian and the VS Code Markdown preview. Because the loopback origin would otherwise let a<script>smuggled in via raw HTML fetch sibling files under the root, miru applies a per-render nonce-based Content-Security-Policy (default-src 'none'; script-src 'nonce-…' 'strict-dynamic'; …) that blocks any non-nonced inline script, inline event handler, or third-party CDN script. Users should still only browser-render Markdown they trust. The TUI view itself executes nothing. The Markdown preview also fetches a Google Fonts stylesheet (Caveat / Patrick Hand / Kiwi Maru) on every load to render the page as a handwritten paper note; this is a passive<link rel="stylesheet">reference, the same trust model as the mermaid script and (when a mermaid block is present) the svg-pan-zoom script that powers click-to-zoom on diagrams. Source-file previews (non-Markdown) inline chroma-generated styles and fetch nothing. Full destination list and how to disable each is in PRIVACY.md. - Config and PATH manipulation.
miru installwrites to~/.config/miru/config.jsonand (optionally) appends a single line to the user's shell rc. The rc edit is idempotent (rcContainsLine) and clearly marked with# added by miru installer. No daemons, no startup hooks.
miru is solo-maintained. To keep changes auditable without a co-maintainer:
- PRs are required for every change to
main(enforced by repository ruleset). Direct push, force push, and branch deletion are blocked. Linear history is required, so merges land without merge commits (squash or rebase only — the project convention is squash). - Required CI checks (all must pass before merge), listed as
workflow / job:ci / test—go test -race ./...(.github/workflows/ci.yml)ci / shellcheck—shellcheck install.sh(ci.yml)ci / actionlint— workflow YAML lint (ci.yml)codeql / codeql (go)— CodeQL+security-extendedon Go code (codeql.yml)codeql / codeql (actions)— CodeQL on workflow files (codeql.yml)govulncheck / govulncheck— Go vulnerability database scan (govulncheck.yml)gitleaks / scan— secret leak scan (gitleaks.yml)
- Required conversation resolution — any reviewer thread on a PR must be resolved before merge.
- Required approving reviewers: 0, because GitHub disallows the PR author from approving their own PR and there is no co-maintainer. The gap is intentionally filled by automated reviewers, not by a rubber-stamp human:
- Copilot code review (ruleset-triggered) runs on every PR open and re-runs on push.
- OpenAI Codex code review (GitHub App + Codex Cloud) runs on every PR open.
AGENTS.mdat the repo root supplies the calibration (focus areas + "what NOT to flag" + severity rubric) so Codex's output is project-specific.
- Secret + supply-chain scanning runs on every PR and weekly: gitleaks (PR + cron), CodeQL
+security-extended(PR + cron), govulncheck (PR + cron + release gate), OpenSSF Scorecards (cron + branch-protection-rule trigger).
Consequence: the OpenSSF Scorecard Code-Review and Branch-Protection require approvers items score below maximum, and we have accepted this trade-off (the corresponding alerts are dismissed with won't fix and link back here). The substitute controls above are stricter than a single human approver in several axes (Copilot + Codex catch issues humans miss; CI gates can't be waived) and they cost the maintainer nothing per PR.
- Issues in upstream dependencies that are not exploitable through miru's API. Report those to the upstream project; we will track via dependabot.
- Vulnerabilities in third-party tools (
mermaid.jsvia CDN, the user's terminal, the browser). We do not control these and cannot patch them. - Theoretical CPU side-channel attacks against the Go runtime.