<!-- markdownlint-disable MD013 -->
<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2026 ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG, Cologne
-->
<!-- markdownlint-enable MD013 -->
# Troubleshooting Guide — BSI Grundschutz++ OSCAL Viewer
This guide helps you find the cause of a problem, not only make the
symptom go away. Section 1 describes the method; the sections after it
are organised by symptom and quote the messages the viewer prints, so
you can search this page for the text you see. For flags and settings
see the Administrator Guide; for how the code is built see the
Developer Guide.
## 1. Finding the root cause
Work through the same five steps every time:
1. **Reproduce.** Note the exact command line, the environment
variables that start with `GSV_` or `MEILI_`, and the URL or action
that fails. A problem you cannot reproduce cannot be confirmed fixed.
2. **Collect facts.**
- `grundschutz-oscal-viewer --version` names the release.
- **Info → System Information** (`/system-info`) shows the platform,
listen address, uptime and the active search engine.
- The log file `<log-dir>/grundschutz-oscal-viewer.log` (default
`./ndaal/log/`) holds everything the console showed.
- Restart with `RUST_LOG=debug` for connection-level detail.
3. **Isolate the layer.** A request passes, in order, the TCP listener,
TLS 1.3, the Host-header guard, the router and the page handler.
The first layer that fails is where the cause lives: no connection
at all points to the listener, a certificate error to TLS, a `403`
to the Host guard or the same-origin check, a `404` to the route.
4. **Change one thing.** Remove flags and variables until the problem
disappears; the last one removed is the trigger. Precedence is CLI
flag, then environment variable, then built-in default, so a stale
variable in a service unit can override what you think you set.
5. **Confirm and prevent.** Repeat the original reproduction. If the
cause was a defect, report it (section 10) so a test can pin it.
## 2. The viewer does not start
| Message | Cause | Fix |
| --- | --- | --- |
| `refusing to bind non-loopback address '…'` | `--bind`/`GSV_BIND` names an address other than `127.0.0.1` or `::1`. The viewer has no authentication, so this is refused by default. | Bind to loopback, or pass `--allow-non-loopback` and put an authenticating reverse proxy in front (Administrator Guide §3.2). |
| An "address in use" error | Another process (often an earlier viewer) holds the port. | Find it with `lsof -i :8228` (macOS/Linux) or `netstat -ano` (Windows), stop it, or start with `--port <other>`. |
| `unknown option: … (see --help)` | A misspelled flag or a missing value. | Compare with `--help`. Flags are not guessed. |
| `encryption key error: … must contain exactly 32 raw bytes or 64 hex characters` | The file given to `--encryption-key-file` has the wrong length, often a trailing newline in a raw key or a short hex string. | Regenerate the key as described in Administrator Guide §11.5 and keep the file mode at `600`. |
Startup problems that do **not** stop the viewer but turn a feature off:
- `annotation store unavailable — annotations disabled` — the database
path is not writable, sits on a read-only volume, or is locked by
another process. Check the path in `--db` / `GSV_DB` and its
directory permissions. An **empty** `GSV_DB` disables annotations on
purpose.
- `log file unavailable — logging to the console only` — `--log-dir`
cannot be created or written. Point it at a writable directory.
## 3. The browser cannot open the viewer
**The browser warns about the certificate.** Expected: unless you pass
your own certificate, the viewer creates a self-signed one at startup.
Accept it for local use or install a proper certificate
(Administrator Guide §3.2.1).
**The page does not load at `http://…`.** The viewer speaks HTTPS only;
there is no plaintext fallback. Use `https://127.0.0.1:8228/`.
**`403` when you use a host name or a reverse proxy.** The anti-DNS-
rebinding guard accepts only the bind address and its loopback aliases
in the `Host` header. Add the name you use with
`--allowed-host <name>` or `GSV_ALLOWED_HOSTS`.
**Another machine cannot connect.** The viewer listens on loopback by
default and is therefore unreachable from the network. See section 2
for `--allow-non-loopback`.
**Connections drop under load.** The log shows
`connection limit reached — shedding connection`. The viewer caps
concurrent connections to stay responsive; reduce the parallelism of
the client or load test.
**Health checks.** `GET /healthz` answers `200 ok` while the process
serves; `GET /readyz` also checks that the annotation database answers.
Use them to tell "process down" from "page broken" and "database
unreachable".
## 4. Search
**System Information says `built-in (Meilisearch configured, index not
ready)`.** You passed `--meili-url`, but the viewer could not reach
Meilisearch or the index is still being built. The built-in search
keeps working in the meantime. Check that Meilisearch runs
(`curl <url>/health`), that the key is right, and read the log for the
indexing result.
**`loading a native root certificate failed`.** A certificate in the
operating system's store could not be parsed. It is logged and skipped;
only if Meilisearch uses that CA does the connection fail. Pass the CA
explicitly with `MEILI_CA` (Administrator Guide §4.2.1).
**Results look incomplete.** The built-in engine searches the embedded
catalog only. Annotations are searched on the **Annotations** page.
## 5. Annotations, uploads and templates
**There is no Annotations workspace.** Annotations need a database. An
empty `--db` / `GSV_DB` turns them off, and so does the startup message
in section 2.
**A save or upload fails with `403 cross-origin request rejected`.**
Write requests must come from the viewer's own pages. A browser
extension, a bookmarklet or a proxy that rewrites the `Origin` header
triggers this. Retry from the page itself.
**A save or upload fails with `413`** (for example
`annotation too large to store`). The annotation text or the uploaded
file exceeds the size limit. Shorten the text or reduce the image.
**An upload fails with `422`.** The file failed validation: wrong type,
not valid UTF-8 where text is expected, an SVG with active content, or
a malware-scanner hit. The response text names the reason.
**Startup warns about the scanners.**
- `upload scanning: ClamAV (clamscan) only — no active YARA pass` or
`upload scanning: YARA (yr) only — clamscan not found on PATH`:
one scanner is missing. Install it (Administrator Guide §10).
- `YARA rules auto-fetch failed — continuing without YARA`: the host is
offline or the download was blocked. Provide rules with
`GSV_YARA_RULES` and silence the fetch with `--no-fetch-yara-rules`.
- `freshclam: cannot create DB dir — skipping update`: the signature
directory is not writable. Fix the permission or use a bundled
database with `--clamav-db-dir` (Administrator Guide §10.5).
**A template is missing from the template list.** The log shows
`annotation template skipped` with the reason, or
`annotation template import failed` for the whole directory. Templates
pass the same checks as uploads.
**`--seed-demo requested but annotations are disabled … nothing
seeded`.** Demo data needs the database; see the first item.
## 6. Database dumps and restore
| Message | Cause | Fix |
| --- | --- | --- |
| `… has no checksum sidecar (.sha-256/…); restore only from a dump whose sidecars are intact` | Since 1.6.54 a restore requires the checksum files written next to the dump. | Copy the dump **together with** its `.sha-256`, `.sha-512`, `.sha3-512`, `.blake3-512` and `.shake256-512` files. |
| `checksum mismatch: … records …, the file hashes to …` | The dump changed after it was written: a truncated copy, a text-mode transfer or tampering. | Restore from another copy. Do not edit a dump by hand. |
| `checksum sidecar … is empty` / `… is unreadable` | A sidecar was damaged in transit. | Copy the dump and its sidecars again. |
| `restore destination … already exists` | Restore never overwrites a database. | Restore to a new path, or move the existing file away first. |
| `unrecognised dump file (expected .db, .sql or .sql.gz)` | The file is not a dump the viewer wrote. | Pick a file from the dump directory. |
| `… exceeding the …-byte cap` | The dump is larger than the restore limit of the Export menu. | Restore it offline instead: stop the viewer and copy the `.db` backup into place (Administrator Guide §11.3). |
## 7. Updating
**`--check-update` prints `could not reach the update host`.** The
host has no route to `gitlab.com`, or a proxy blocks it. This is
reported, not an error (exit code 0).
**`--self-update` stops with `no SHA-256 for <asset> in the release
SHA256SUMS; refusing to install an unverified binary`.** The checksum
list for the newest release had no line for your platform, so the
viewer refused to install a binary it could not verify — the safety
check working, not a broken download. Versions up to 1.6.59 read that
list from a file committed to the repository, which releases 1.6.56 to
1.6.58 did not update; from 1.6.60 on the list is read from the release
itself. If you still see the message, update once by hand:
1. Download the binary for your platform and
`grundschutz-oscal-viewer-<version>-SHA256SUMS.txt` from the release
page.
2. Verify it: `shasum -a 256 -c` on the matching line.
3. Replace the old binary (Administrator Guide §3.5).
**`--self-update is disabled by policy`.** `--no-self-update` or
`GSV_NO_SELF_UPDATE` is set, usually by a package or a service unit.
Update through that package manager instead.
**Permission denied while replacing the binary.** Self-update replaces
the file in place and needs write access to it. Run it as the owner of
the binary, or update through the package manager.
## 8. Display and language
**Parts of the page are in English.** The UI falls back to English for
any text a language file lacks. Every release is tested for complete
language files, so this indicates a defect; report it with the
language you selected.
**A page shows raw `#` headings and `**` markers.** That is intended for
the README, the Changelog and the guides: they are shown as plain
Markdown, never rendered, so no markup from a document can reach the
browser.
**Icons or fonts are missing.** All assets are embedded and served from
`/static/`. If they fail, a proxy or browser extension is blocking
`/static/` or rewriting the Content-Security-Policy.
## 9. Operating-system specifics
- **macOS:** "cannot be opened because the developer cannot be
verified" — clear the download flag with
`xattr -d com.apple.quarantine <file>`.
- **Linux:** `Permission denied` when starting — run `chmod +x <file>`
once.
- **Windows:** SmartScreen warns about an unknown publisher — choose
**More info → Run anyway** after checking the checksum.
## 10. Reporting a problem
Include:
- the output of `grundschutz-oscal-viewer --version`;
- the text of **Info → System Information**;
- the command line and the `GSV_*` / `MEILI_*` variables (remove keys
and passwords);
- the log lines around the failure, ideally with `RUST_LOG=debug`;
- the steps that reproduce it.
Report a security problem privately to ndaal (contact details under
**Info → Imprint**), not in a public issue.