<!-- 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 -->
# User Guide — BSI Grundschutz++ OSCAL Viewer
This guide is for compliance officers, auditors, information-security
officers and anyone else who wants to **read, filter and search the BSI
Grundschutz++ catalog** in a browser. No technical background is
required beyond starting a program and using a web browser.
For installation on servers, Meilisearch setup, catalog updates and
redistribution rules see the
[Administrator Guide](administrator_guide.md).
## 1. What the viewer is
The viewer is a single program (one file, no installation) that serves
the complete official **Anwenderkatalog Grundschutz++** of the German
Bundesamt für Sicherheit in der Informationstechnik (BSI) as a local
website. The catalog — **998 controls in 20 practices**, in the
machine-readable **OSCAL 1.1.3** format — is built into the program
itself. Nothing is downloaded, nothing is sent anywhere: the viewer
works entirely offline on your own machine.
The catalog content (requirement texts, guidance, titles) is **German**,
as published by the BSI; the user interface around it is English. The
catalog data is © BSI, licensed CC-BY-SA-4.0.
### 1.1 Grundschutz++ versus the classic IT-Grundschutz-Kompendium
Grundschutz++ is the BSI's modernised, machine-readable catalog from
the *Stand-der-Technik-Bibliothek*. Where the classic
IT-Grundschutz-Kompendium organises requirements into building blocks
(*Bausteine*) across layers, Grundschutz++ groups them into 20 process-
and topic-oriented **practices** (e.g. Governance und Compliance,
Risikomanagement, Notfallplanung), and every requirement carries
structured attributes — a modal verb (MUSS / SOLLTE / KANN), a security
level, an effort rating and tags — published as OSCAL JSON so tools can
process it directly. This viewer shows the Grundschutz++ catalog, not
the classic Kompendium.
## 2. Installing and starting the viewer
You do **not** have to be a developer: the viewer is a single,
self-contained file. Choose **one** of the two ways to get it.
### 2.1 Option A — `cargo install` (if you have Rust)
With [Rust](https://www.rust-lang.org/tools/install) 1.93 or newer:
```sh
cargo install grundschutz-oscal-viewer
```
The program `grundschutz-oscal-viewer` is now on your `PATH`.
### 2.2 Option B — download the ready-made binary (no Rust needed)
From the
[**Releases** page](https://gitlab.com/vPierre/ndaal_public_bsi_grundschutz_oscal_viewer/-/releases),
download the **one file** for your system:
| Your system | File to download |
| --- | --- |
| **Windows** 10/11 (most PCs) | `…-x86_64-pc-windows-gnu.exe` |
| **Windows** on ARM | `…-aarch64-pc-windows-gnullvm.exe` |
| **Linux** (most PCs/servers) | `…-x86_64-unknown-linux-musl` |
| **Linux** on ARM (e.g. Raspberry Pi 64-bit) | `…-aarch64-unknown-linux-musl` |
| **macOS** Intel | `…-x86_64-apple-darwin` |
| **macOS** Apple Silicon (M1/M2/M3/M4) | `…-aarch64-apple-darwin` |
- **Windows:** double-click the `.exe`. If SmartScreen warns, click
**More info → Run anyway** (the binary is unsigned).
- **Linux:** `chmod +x` the file once, then run `./<the-file>`.
- **macOS:** `chmod +x` the file once, run
`xattr -d com.apple.quarantine <the-file>` to clear the download flag,
then run `./<the-file>`. If it is still blocked, allow it under
**System Settings → Privacy & Security → Open Anyway**.
### 2.3 Starting it and opening the UI
1. Run the program `grundschutz-oscal-viewer` (double-click on
platforms that allow it, or start it from a terminal). It prints
`listening on https://127.0.0.1:8228` and keeps running.
2. Open your browser at **<https://127.0.0.1:8228>** (same machine
only — that is intentional). Because the certificate is self-signed
and regenerated at every start, the browser warns **once** — click
**Advanced → Proceed / Continue to 127.0.0.1**. This is safe; the
connection never leaves your computer. You should then see the home
page:

3. To stop the viewer, press `Ctrl-C` in its terminal window.
If port 8228 is taken, start it with another port:
```sh
grundschutz-oscal-viewer --port 9000 # then open https://127.0.0.1:9000
```
`grundschutz-oscal-viewer --help` lists all options; everything beyond
the port (bind address, Meilisearch, catalog export) is covered in the
Administrator Guide. While you browse, nothing is written to disk and
no connection to the internet is made.
## 3. The pages at a glance
Every page is reachable from the top navigation bar; the address bar
URLs below also work directly.
| Page | URL | What it shows |
| --- | --- | --- |
| Home | `/` | Search, count cards, practice overview |
| Controls | `/controls` | Filterable list of all 998 controls |
| Control detail | `/control/{id}` | Every field of one control |
| Raw control JSON | `/control/{id}/raw.json` | One control as raw JSON |
| Practices | `/practices` | All 20 practices as cards |
| Practice detail | `/practice/{id}` | One practice, groups + controls |
| Search | `/search` | Full-text search over the catalog |
| Statistics | `/stats` | Catalog-wide distributions |
| Metadata | `/metadata` | OSCAL metadata + download button |
| Export | `/export` | Export the catalog, a practice or a control as JSON / Markdown / ODT / PDF / ZIP bundle (+ checksums). *Start export* writes server-side; *Start download* sends the file (or a ZIP) to your browser |
| Catalog download | `/catalog.json` | The embedded catalog file |
| Info menu | see section 11 | About, system, legal, license |
Replace `{id}` with a real id: `/control/GC.1.1` opens control
GC.1.1, `/practice/GC` the practice GC. The Info menu pages are
`/about`, `/system-info`, `/privacy`, `/security`, `/license`,
`/imprint` and `/changelog` (section 11).
## 4. The home page (`/`)
The home page has three areas:
- **Search box** — type here and results appear live underneath while
you type (see section 10). Pressing the **Search** button opens the
full search page. The subtitle shows the embedded **catalog version**
(a BSI generation timestamp).
- **Four count cards** — total controls, MUSS, SOLLTE and KANN counts.
Each card is a link: clicking *MUSS* opens the control list already
filtered to `/controls?modal=MUSS`.
- **Practices (domains) table** — all 20 practices with their control
counts and a MUSS / SOLLTE / KANN breakdown. Clicking a practice id
or title opens its detail page.
## 5. The controls list (`/controls`)
The control list shows all controls that match the active filters, 50
per page, in catalog order. Sub-controls (e.g. `GC.5.1.1` under
`GC.5.1`) are indented with a small arrow. Each row shows id, title,
requirement badge, security level, effort, practice and group; id and
title link to the control detail page.
### 5.1 The filter panel
| Filter | Parameter | Example |
| --- | --- | --- |
| Practice (domain) | `practice` | `/controls?practice=GC` |
| Requirement (modal verb) | `modal` | `/controls?modal=MUSS` |
| Security level | `level` | `/controls?level=erhöht` |
| Effort | `effort` | `/controls?effort=0` |
| Tag | `tag` | `/controls?tag=Cryptography` |
| Text filter | `q` | `/controls?q=Backup` |
How each filter is operated:
- **Practice** — choose from the dropdown; the list reloads
immediately, no extra click needed.
- **Requirement** — click one of the MUSS / SOLLTE / KANN pills;
click the active pill again to switch the filter off.
- **Security level** — click the `normal-SdT` or `erhöht` pill.
- **Effort** — click one of the `0`–`5` pills.
- **Tag** — click a tag badge on any control detail page.
- **Text filter** — type keywords and press **Filter**.
Notes:
- **Filters combine.** Every filter narrows the result further, and
switching one filter keeps all the others. Examples:
- `/controls?modal=MUSS&practice=GC` — all mandatory controls of the
Governance-und-Compliance practice.
- `/controls?level=erhöht&effort=5` — high-effort controls for
elevated security needs.
- `/controls?practice=NOT&q=Wiederanlauf` — emergency-planning
controls mentioning "Wiederanlauf".
- The active pill is shown filled; clicking it again removes that
filter. **Clear all filters** resets everything at once.
- The **text filter** matches controls that contain *all* entered
words (in title, id, requirement text, guidance or tags), regardless
of upper/lower case.
- The **tag filter** matches the tag exactly. You normally set it by
clicking a tag on a control page rather than typing it; special
characters (spaces, umlauts) are encoded automatically.
- The match counter above the table always shows how many controls
match the current filters.
### 5.2 Pagination
Results come in pages of 50. Use the **Previous / Next** buttons below
the table; the page indicator shows e.g. "Page 2 of 7". The active
filters survive paging — `page` is just another URL parameter
(`/controls?modal=SOLLTE&page=2`).
## 6. The control detail page (`/control/{id}`)
Open any control (e.g. `/control/GC.1.1`) to see **every field the
catalog stores for it**:
- **Breadcrumb** — Home → practice → group → control, each level
clickable.
- **Header badges** — requirement level (MUSS / SOLLTE / KANN),
security level, effort and the OSCAL control class (see section 12
for how to read them). A control also shows an **Updated** badge when
its requirement content changed versus the previous catalog edition,
or a **New** badge when the control is new in the current edition.
- **Sub-control note** — if the control is nested, a link to its
parent control.
- **Statement (Anforderung)** — the requirement text itself. Where the
OSCAL source uses placeholders, the viewer substitutes the parameter
values into the text so it reads naturally.
- **Guidance (Erläuterung)** — the BSI's explanatory text, when
present.
- **Previous version** — for a control flagged **Updated**, a
highlighted card shows the control's content (title, requirement
level, statement, guidance and parameters) from the previous catalog
edition, so you can compare it directly with the current content
above. The card is stamped with the previous edition's version.
- **Tags** — clickable badges; clicking one opens the control list
filtered to that tag.
- **Statement properties / Control properties** — all OSCAL `props` as
name/value tables (hover a name to see its namespace, when one is
set).
- **Parameters** — the control's OSCAL parameters with id, label and
values.
- **Sub-controls** — links to nested controls, when present.
- **Raw OSCAL JSON** — the complete control exactly as it appears in
the catalog file, pretty-printed at the bottom of the page. The
**Raw JSON** / **Open** buttons serve the same content on its own at
`/control/{id}/raw.json` — useful for copying into other tools.
Nothing is hidden: every JSON byte of the control is visible here.
## 7. Practices (`/practices` and `/practice/{id}`)
A *practice* is one of the 20 thematic domains of Grundschutz++ (its id
is the prefix of every control id — `GC.1.1` belongs to practice `GC`).
- The navbar **Practices** menu opens a dropdown with all 20 practices
for direct access; **All practices** leads to the overview.
- **`/practices`** shows one card per practice with the full BSI
description, the number of groups and controls, and the
MUSS / SOLLTE / KANN breakdown.
- **`/practice/{id}`** (e.g. `/practice/RISK`) shows one practice: its
description, its summary counts, and every group with the controls it
contains. Sub-control counts are indicated per control, and each
control links to its detail page.
## 8. Statistics (`/stats`)
The statistics page shows catalog-wide distributions as horizontal
bars, each with absolute count and percentage:
- **Requirement levels** — MUSS, SOLLTE, KANN and controls without a
modal verb (structural parent controls).
- **Security levels** — `normal-SdT` versus `erhöht`.
- **Effort levels** — controls per effort rating `0`–`5`.
- **Per practice** — control count and MUSS / SOLLTE / KANN breakdown
for each of the 20 practices, with links to the practice pages.
Use it to get a feel for the catalog's weight distribution — for
example how much of a practice is mandatory.
## 9. Metadata and catalog download (`/metadata`, `/catalog.json`)
The metadata page shows the catalog's own bookkeeping: title, version
(the BSI generation timestamp), OSCAL schema version and last-modified
date, followed by the **raw OSCAL `metadata` block** and — when present
— the **back-matter** (resources/references), both as pretty-printed
JSON.
The **Download embedded catalog (JSON)** button saves the complete
catalog as `grundschutz-plus-plus-catalog.json`. The same file is
served at `/catalog.json` — **byte-for-byte identical** to what is
embedded in the program, so you can feed it to other OSCAL tools or
archive it as evidence.
### 9.1 The Export menu (`/export`)
The **Export** page produces richer, ready-to-share files on the
machine running the viewer. Choose three things on the form and press
**Start export**:
1. **Scope** — the whole Grundschutz++ catalog, one **practice**
(pick it from the drop-down), or a single **control** (type its id,
e.g. `KONF.2.1`).
2. **Formats** — any of **JSON** (raw OSCAL), **Markdown**, **ODT**
(OpenDocument Text, opens in LibreOffice/Word) and **PDF**.
3. **Checksum sidecars** — one or more of `.sha-256`, `.sha-512`,
`.sha3-512`, `.blake3-512` and `.shake256-512`. Each is a small text
file next to the export holding its checksum, so a recipient can
verify the file was not altered.
There is also an **Overwrite existing files** checkbox, ticked by default,
so re-running the same export simply replaces the previous files. Untick it
to protect existing files: the export then reports an error (rather than
replacing anything) if a file of the same name already exists.
The files are written into the server's export directory (set by the
administrator; by default a `grundschutz-oscal-viewer/export` folder in
the home directory of the account running the viewer); the page then lists
each file written with its size and how many sidecars it got. Because
this writes on the server, it is meant for the person running the
viewer, not for remote visitors.
**Export database dump.** When annotations are enabled (the administrator
started the viewer with `--db`), the Export page also has an **Export DB dump
with sidecars** button. It writes a complete backup of the annotation store —
both a SQLite snapshot (`annotations-<timestamp>.db`) and a portable SQL text
dump (`annotations-<timestamp>.sql`) — into the dump directory (default
`./ndaal/dumps`, changeable in the field), each with the checksum sidecars you
tick (all five on by default). The `.db` snapshot is the handy backup; the
administrator can restore it later. The timestamp is in UTC, so successive dumps
never overwrite each other.
## 10. Search
You can search from three places: the box on the home page, the box in
the navigation bar (available on every page), and the search page
`/search` itself.
- **Live results** — on the home page and the search page, results
appear automatically about a third of a second after you stop
typing; no Enter required. (Technically the page fetches
`/search/partial` in the background.)
- **What is searched** — control ids, titles, requirement texts
(statements), guidance and tags, across the entire catalog. Searches
ignore upper/lower case. Try a control id (`GC.1.1`), a German
keyword (`Backup`, `Notfall`) or a tag (`Cryptography`).
- **Result cards** — each hit shows the control id, title, its
badges, a link to its practice and a short text snippet around the
match. Clicking the id or title opens the control.
- **"Showing the first 50"** — the hit counter shows the *total*
number of matching controls, but the page lists at most the **first
50**. When more match, it says e.g.
"127 hits — showing the first 50": make the query more specific (add
a word) or use the control-list filters (section 5) instead, which
paginate through *all* matches.
- **Which engine answered** — a small badge next to the hit count says
`built-in search` or `Meilisearch search`:
- **built-in** (always available, works fully offline) — finds
controls containing *all* of your words; very long queries are
capped (256 characters, 16 words).
- **Meilisearch** (optional, set up by an administrator) — adds typo
tolerance and relevance ranking. The search page shows
"Meilisearch index active" when it is on. If Meilisearch is ever
unavailable, the viewer falls back to the built-in engine by
itself — search never breaks.
## 11. The Info menu
The **Info** dropdown at the right end of the navigation bar (and the
footer links) opens the reference pages:
| Entry | URL | Content |
| --- | --- | --- |
| About | `/about` | Viewer + catalog versions and counts |
| System info | `/system-info` | OS, listen address, search engine |
| Privacy | `/privacy` | Privacy statement |
| Security | `/security` | Security properties of the viewer |
| License | `/license` | Program + catalog data licenses |
| Imprint | `/imprint` | Legal notice (Impressum, German) |
| Changelog | `/changelog` | Version history of the viewer |
In more detail:
- **About** shows what the viewer is, its program version, the
embedded catalog title and version, the control and practice counts
and the embedded catalog size.
- **System info** shows operating system, architecture, listen
address, uptime and the **active search engine** (built-in or
Meilisearch) — the quickest way to check the search setup.
- **Privacy** states that the viewer collects nothing and calls no
external services.
- **License** shows the full Apache-2.0 license of the program and
the BSI catalog license record (CC-BY-SA-4.0).
- **Imprint** is the legal notice, in German as legally required.
## 12. Reading the badges: MUSS / SOLLTE / KANN, security level, effort
Every control carries up to three colored badges. They reflect the
BSI's own classification in the catalog:
### 12.1 Requirement level (modal verb)
| Badge | Color | BSI meaning |
| --- | --- | --- |
| **MUSS** | green | Mandatory — must be fulfilled |
| **SOLLTE** | yellow | Should be fulfilled; deviations need reasons |
| **KANN** | red | Optional — may be implemented |
In the BSI's convention, **MUSS** marks an unconditional requirement
with no room for discretion. **SOLLTE** is a strong recommendation:
the requirement should be fulfilled, and any deviation must be
carefully justified and documented. **KANN** is optional — implement
it where it adds value; omission needs no justification. Some
structural parent controls carry no modal verb at all — the
obligation then sits in their sub-controls.
### 12.2 Security level
| Badge | Color | Meaning |
| --- | --- | --- |
| **normal-SdT** | grey | Baseline, state-of-the-art protection |
| **erhöht** | orange | Elevated protection needs |
`normal-SdT` controls describe baseline protection following the
current state of the art (*Stand der Technik*) and are relevant for
every organisation. `erhöht` controls are additional measures for
organisations or assets with increased security requirements. A
practical reading: start with `normal-SdT`; the `erhöht` controls are
what you add on top when your risk situation demands more
(`/controls?level=erhöht` lists them all).
### 12.3 Effort
The **Effort** badge (`0`–`5`) is the catalog's relative implementation
effort rating, from `0` (minimal effort) to `5` (very high effort). It
helps with planning and prioritisation — for example,
`/controls?modal=MUSS&effort=0` shows mandatory controls that are cheap
to implement: natural quick wins.
## 13. Dark mode
The **moon icon** in the navigation bar switches the whole UI to a dark
theme; it turns into a **sun icon** to switch back. The choice is
remembered by your browser, so the viewer reopens in your preferred
theme. No setting leaves your machine.
## 13a. Language (English / German / French)
The **globe icon** in the navigation bar opens a language menu —
**English**, **Deutsch** and **Français**. Picking one re-renders the
interface chrome (menus, buttons, labels, headings) in that language and
remembers it in a cookie, so the viewer reopens in your choice. Until you
pick one, the viewer follows your browser's preferred language (the
`Accept-Language` header), defaulting to English.
The BSI catalog itself — control text, the requirement verbs **MUSS /
SOLLTE / KANN**, the security level **erhöht** — stays in German, the
language the BSI publishes it in. Your own annotation text is never
translated either.
## 14. Navigating without the keyboard
The viewer is designed so that everything except free-text input works
by clicking alone:
- all pages are reachable from the navigation bar and its dropdowns,
- every count card, table row, badge, tag, breadcrumb and practice
link is clickable,
- filters are toggled by clicking pills, and the practice dropdown
applies itself on selection — no submit button needed,
- pagination uses Previous / Next buttons.
Typing is only ever needed for the two free-text fields (search and
the text filter). Conversely, every view you click together is
reflected in the URL, so any filtered list or control page can be
**bookmarked or shared** simply by copying the address — a colleague
with the same viewer sees exactly the same view.
## 15. Annotations and reusable templates
Annotations let you attach your **own notes** to the catalog: a piece of
Markdown text — plus optional images — that belongs to the whole
catalog, to one practice, or to a single control. Use them to record how
your organisation implements a requirement, who is responsible, or what
evidence exists.
### 15.1 Turning annotations on
Annotations are **off by default**. They are saved in a local SQLite
database file, so the viewer must be started with `--db <file>`, for
example:
```sh
grundschutz-oscal-viewer --db ~/grundschutz-oscal-viewer/annotations.db
```
Without `--db` the catalog stays read-only and the editor is shown
disabled. The database lives only on your machine; nothing is sent
anywhere.
### 15.2 The Annotations page (`/annotations`)
Open **Annotations** from the navigation bar (right of Export). One page
edits all three levels — the level is **derived from the Id** you enter,
so you cannot pick the wrong one:
- **empty Id** → the whole catalog,
- a practice Id such as `GC` → that practice,
- a control Id such as `GC.1.1` → that control.
If the Id matches no practice or control, the page falls back to the
whole catalog and shows a warning naming the bad Id. The editor has a
**Source ⇄ Rendered** view with a live preview, a MUSS / SOLLTE / KANN
modal that defaults from the control, a **Maturity Level** picker (the six
CMMI-style levels 0 Incomplete, 1 Performed, 2 Managed, 3 Established,
4 Predictable, 5 Innovating, defaulting to 1 Performed), a draft / release
toggle, and a button that copies the control's or practice's own text into
the editor as a starting point. Each saved version stores its own maturity
level alongside its modal and status.
### 15.3 Writing Markdown and adding images
Type ordinary **Markdown** — headings, lists, bold, links. It is
rendered safely: scripts and other active content are stripped, so a
note can never run code in your browser.
You can also upload **PNG or SVG images**. They appear **inline** in the
rendered note (scaled to fit a single DIN A4 page, on screen and in
print). Every upload is checked before it is stored: the file type is
verified, and — when the **ClamAV** and **YARA** scanners are installed
(see the Administrator Guide) — it is scanned for malware. Anything that
fails is rejected and never saved.
### 15.4 Reusable templates
If the viewer was started with a `--templates-dir`, the editor shows an
**Insert a template** dropdown. Choosing an entry inserts that
template's Markdown **at your cursor** (or at the start of the field if
you have not clicked into it). Templates are ordinary `*.md` files
loaded from that directory at start-up, each admitted through the **same
safety gate** as an uploaded image — valid text, a sensible file type, a
size cap and the malware scan — so only clean templates ever appear.
### 15.5 Versions
Saving is **append-only**: every **Save** creates a new numbered,
time-stamped revision and never overwrites the last one. Nothing is ever
lost, and you can browse the full history.
### 15.6 Seeing annotations on a control page
A control that has an annotation shows an **annotation card** with the
same **Source** and **Rendered** panes and a **version picker** to step
through every saved revision (the release version is labelled as such).
A control with no annotation yet shows an **"Add an annotation"** link
that opens the editor already pointed at that control, so you can start a
note in one click.
### 15.7 Demonstration data
If the viewer was started with the `--seed-demo` option (an
administrator choice — see the Administrator Guide), the Annotations
workspace comes **pre-filled with example notes** at every level: a
"Catalog overview", a "Practice … overview" for each domain, and a "Demo
annotation — …" for each control, each with a small **demo badge** image
and a short draft → release → draft history. They exist so you can try
out the Source ⇄ Rendered view, the version picker and the image display
without writing anything first.
You can recognise them by the **"Demo annotation"** / **"overview"**
headings and the line *"Seeded by `--seed-demo`"*. They are only added to
levels that had **no annotation yet**, so your own notes are never
touched — and you can edit or replace any demo note exactly like your
own. If you do not see them, the operator simply did not enable the
option; nothing is missing from the catalog itself.