BLA
BLA는 오픈소스 Blue Defense MCP와 CLI로, 소프트웨어 자산을 자동 AI 리버스 엔지니어링으로부터 보호합니다
스위스 문서의 개인정보를 마스킹·익명화하는 무료 오픈소스 소프트웨어, 로컬 실행, GPU 필요 없음
Freie Open-Source-Software: maskiert und anonymisiert Personendaten in Schweizer Dokumenten, bevor du sie mit ChatGPT & Co. teilst. Lokal, ohne GPU.
보안GitHub @semprerudi온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
저장소 정보는 아직 수집하지 않았습니다.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
Free, open-source PII masking for Swiss documents (de/fr/it/en). Runs locally on the CPU – no GPU, no cloud. Detects AHV numbers, names, addresses and IBANs and pseudonymises them before you ask ChatGPT, Claude & Co.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗English · Deutsch
Free, open-source PII masking for Swiss documents: on your own device, on the CPU, with no GPU and no network while it runs. Four languages: German, French, Italian, English.
You have a letter, an email thread or a PDF and want to ask an AI tool about it, but the names, social security numbers and addresses should stay behind. MASCHERA replaces them with placeholders:
Sehr geehrte Frau Brülhart -> Sehr geehrte Frau [FULLNAME_1]
AHV 756.1234.5678.97 -> AHV [AHVN13_1]
Lindenstrasse 12, 3011 Bern -> [STREET_1] [BUILDINGNUM_1], [ZIPCODE_1] [CITY_1]
You give the masked text to the service of your choice. Paste the answer back, and MASCHERA puts the real values in again.
Made for everyone who works with Swiss documents: federal, cantonal and municipal administrations, companies, law firms, associations and private individuals. Useful wherever the Swiss Federal Act on Data Protection (FADP/revDSG) or the GDPR applies: personal data stays out of the text you share. It does not replace your own check.
Detection runs entirely on your device. While it runs, nothing is downloaded and nothing is sent: no fonts, no icons, no analytics. There is no endpoint that sends text anywhere, and there never will be; the way out always goes through your clipboard.
⚠️ The model is 1.2 GB, and there are two ways to ship it. The AppImage and the Windows installer carry it: download, start, done. The Flatpak fetches it once, on first start: from a named source, with a checksum, and only after you agree. If you are not on Wi-Fi right now, decline and fetch it later. After that, both are equally offline.
maschera is Italian for mask and carries the Swiss country code CH in the middle.
⚠️ Pseudonymisation, not anonymisation. As long as the dictionary exists, the process can be reversed, and under Swiss data protection law (revDSG) the data remain personal data. That is why the button says "Mask".
Built on rizzo-pii by Simone Rizzo (MIT). Not affiliated with the original project; no rights to the "Rizzo AI Academy" name are claimed.
The shipped model (runs/ch-v63b) was tested in two ways. Both results
are here, including the less flattering one.
| practice texts (synthetic) | real documents (gold documents) | |
|---|---|---|
| Size | 2000 generated texts, 29,210 personal details (gold spans) | 9 real documents, 280 personal details (gold spans) |
| Left visible (leak rate) | 0.02 %: 13 details missed in whole or in part | 1.27 %: 6 details missed in whole or in part |
| Overall score (micro-F1, 1 = perfect) | 0.997 | 0.736 |
| Masked too much | 84 places | 95 places |
| Anyone can recheck it | yes | no |
The leak rate is the share of letters and digits belonging to personal details that can still be read after masking. The overall score sums up how often MASCHERA finds the right places and how often it gets it wrong.
The practice texts are generated by MASCHERA itself, from templates and lists of names. Anyone with the source code gets exactly the same numbers with this command:
python3 tools/mess_synthetisch.py --model runs/ch-v63b --n 2000
The numbers in the table were measured this way with MASCHERA 1.0.1. The texts depend on the templates and the generator: if those change, the numbers change with them, and the table has to be measured again.
⚠️ The practice texts are familiar to the model. They are built from the same templates as the texts it learned from. It has never seen the names and numbers in them, though, so it really has to recognise them and cannot know them by heart. Real letters are still harder. That is why there is a second test.
The real documents are nine letters in which every personal detail was
marked by hand. Because they contain real personal data, they are not
published, so nobody can recheck this number. It is the most honest one,
though. How to build your own test documents: docs/GOLDDOKUMENTE.md
(German).
⚠️ Nine documents are not many. If two models differ by less than about 0.06 in the overall score, that is chance with so few documents, not an improvement.
What this means for you: in real documents a little over one percent of personal details stays visible, not zero. MASCHERA takes the work off your hands, not the responsibility: look at the masked text before you pass it on. That is why area 02 shows every finding in colour, with where it came from.
⚠️ Masking too much is not a leak. One word too many hidden makes the text a little harder to read; one word too few gives away a personal detail. When in doubt, MASCHERA hides too much.
ch-v63b, a fine-tuned ModernBERT (jhu-clsp/mmBERT-base), 22
layers, 308 M parameters. Trained on synthetic data generated from the
templates and nomenclatures in this repository. It has never seen a real
document.
The weights are on Hugging Face under MIT: semprerudi/maschera-ch-v63b. They are not part of this repository, because 1.2 GB does not belong in Git.
The training path is open. The training data is not shipped as a file, it is GENERATED. Templates, nomenclatures and generators are in this repository:
python3 tools/generate_dataset.py --n 40000 --rauschen 0.08 \
--zone training --seed 2026 --out dataset.jsonl
python3 tools/generate_dataset.py --n 2000 --rauschen 0.08 \
--zone halten --seed 63 --out eval.jsonl
python3 train.py --data dataset.jsonl --eval-data eval.jsonl \
--out runs/eigenes --max-length 512 --seed 4711
ch-v63b was trained withRead from training_args.bin of the shipped run, not copied from the
defaults:
| Epochs | 3 |
| Learning rate | 3e-5, linear, warmup 0.1 |
| Batch | 8 × 2 (gradient accumulation) |
| Weight decay | 0.01 |
| Precision | bf16 |
| max_length | 512 |
| Training seed | 4711 |
⚠️ train.py defaults to seed 2026; the shipped run used 4711.
Without --seed you get a different model.
⚠️ The seed of the TRAINING SET was not recorded. training_args.bin
records how the model was trained, not on what. The command above
therefore uses 2026; that is a recommendation for rebuilding, not a
statement about the shipped run. The path can be walked; the exact
trail of that one run cannot. Future runs record both:
generate_dataset.py writes its seed into the header of the .jsonl.
⚠️ --zone is not optional. Without it, training and evaluation sets
draw from the same nomenclature, and the numbers would fit "recognises
names" and "memorised the list" equally well.
⚠️ And the same seed is not enough. If the generator changes, the same command produces a different data set; the code version is part of the recipe.
⚠️ --ohne-modell (without model) is not an operating mode. Without
the model only checksums and patterns apply, and every name gets
through. The command line allows it for measurements and says so.
Three ways on Linux, an installer for Windows, and a disk image for macOS. They differ in exactly one question: does the model ship with the package, or is it fetched on first start?
| Package | Model | For whom | |
|---|---|---|---|
| AppImage | about 1.7 GB | included | one executable file; no installation, no dependencies |
| Flatpak | about 0.3 GB, plus the KDE runtime from Flathub | fetched, 1.2 GB | Debian, Ubuntu, Fedora, openSUSE, Arch, wherever Flatpak runs |
| Windows installer | about 1.4 GB | included | Windows 11, 64-bit; installs without administrator rights |
| macOS disk image | about 1.4 GB | included | Macs with Apple silicon (M1 or newer); not signed |
| Docker | about 4.2 GB | included | running as a service; interface in the browser |
If the model is fetched, the first start asks first: it names source, size and destination, and "Later" closes the application without downloading anything. After that everything runs offline. The one exception is yours to choose: "Check for updates" in the menu asks GitHub once for the newest version number, nothing else, and never without that click.
AppImage and Flatpak bring their own libraries and do not depend on what else is installed on the system.
The ready-made files are on the releases page.
chmod +x MASCHERA-latest-x86_64.AppImage
./MASCHERA-latest-x86_64.AppImage
flatpak install --user MASCHERA-latest-x86_64.flatpak
flatpak run ch.maschera.Maschera
The Flatpak fetches the KDE runtime from Flathub when installed.
On Windows, run MASCHERA-latest-x64-setup.exe. The installer is not
signed, so Windows warns on first start: "More info" → "Run anyway".
On macOS (Apple silicon only), open MASCHERA-latest-arm64.dmg and drag
MASCHERA into Applications. The app is not signed, and recent macOS
versions do not let you open it with a right click. Remove the quarantine flag
once, in Terminal:
xattr -dr com.apple.quarantine /Applications/MASCHERA.app
The macOS build is new and has been tested on one Mac so far, including "Copy and open …". Messages from Apple Mail cannot be dragged straight into the window (Mail hands over no file); drag them to the desktop first, then in.
A ready-made image, about 4.2 GB, the model included:
docker run -d --name maschera -p 127.0.0.1:4141:4141 \
ghcr.io/semprerudi/maschera:2.3.1
Then open http://127.0.0.1:4141 in the browser. The server keeps
nothing: no document, no dictionary, no log. To keep your own rules and
settings across restarts, add -v maschera-config:/home/maschera/.config/maschera.
Behind a reverse proxy or under another name than 127.0.0.1, set
MASCHERA_WIRT; see tools/paket/LIESMICH.md (German).
Every release carries SHA256SUMS. Put it next to the files you downloaded
and check them:
sha256sum -c --ignore-missing SHA256SUMS
On macOS, shasum -a 256 MASCHERA-latest-arm64.dmg prints the sum to compare
with the matching line in SHA256SUMS.
The Docker image is signed (keyless, with cosign)
from 1.0.2 on; 1.0.1 is not. The signature is tied to the GitHub account's
noreply address, the same one that appears on the commits:
cosign verify ghcr.io/semprerudi/maschera:latest \
--certificate-identity [email protected] \
--certificate-oidc-issuer https://github.com/login/oauth
Newer images are signed by the repository's own workflow
(.github/workflows/abbild-signieren.yml) instead of by hand. Verify those with
the workflow's identity:
cosign verify ghcr.io/semprerudi/maschera:latest \
--certificate-identity https://github.com/semprerudi/maschera/.github/workflows/abbild-signieren.yml@refs/heads/main \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
From 2.1.1 on, the release workflow (.github/workflows/release.yml) builds and
signs the image when a version is tagged. Verify those with the tag in the
identity:
cosign verify ghcr.io/semprerudi/maschera:2.3.1 \
--certificate-identity https://github.com/semprerudi/maschera/.github/workflows/release.yml@refs/tags/v2.1.1 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
An SBOM (SPDX) for the Docker image and one for the AppImage are attached to each release since 1.0.1.
fish tools/paket/appimage_bauen.fish # AppImage
flatpak-builder --user --install \
--state-dir bau/zustand bau/ziel \
tools/paket/ch.maschera.Maschera.yml # Flatpak
fish tools/paket/bauen.fish # Docker
.\.venv\Scripts\python.exe tools\paket\windows_bauen.py # Windows, on Windows
tools/paket/LIESMICH.md (German) describes each path and the decisions
behind it, including how to fetch the model for the AppImage and Docker
builds. WINDOWS.md (German) describes the Windows build as it was run;
MACOS.md describes the macOS build, which runs on GitHub (workflow Pakete bauen, started by hand).
python3 tools/run_tests.py
31 checks, no dependencies except PyYAML and Flask. No model, no GPU, no downloads needed: 30 pass straight away in a fresh clone.
The 31st needs the model and says so; two more skip one point each as
long as the nomenclature has not been built
(python3 packs/ch/nomenclatures/build.py). Both show up in the output:
a skipped check has checked nothing, and the runner does not count it
as passed.
python3 tools/generate_dataset.py --n 20 --show
Generates training examples from the templates and shows what the result looks like.
The code, its comments and the documentation are in German; the interface speaks all four languages.
specs/ is the full requirements catalog: goals,
requirements, architecture, flows, the contract of the endpoints, training,
interface, security and every decision with its reason, plus a test catalog
generated from the code.
packs/ch/ everything Swiss-specific
taxonomy.yaml 45 tags, frozen BIO order <- source of truth
patterns.yaml regexes with context anchors in four languages
macros.yaml coupled slot groups (@address, @person)
pack.yaml manifest, label contract hash, backbone
adapter.py the pack's interface
validators/ checksums (AHV, UID, IBAN, QR, Luhn, GLN)
generators/ generate valid identifiers
nomenclatures/ build.py + data sources (data NOT in the repo)
templates/ templates per language
core/ language-independent mechanics
recognizers.py regex + anchors + checksums
masking.py masking, dictionary, propagation
injector.py template + nomenclature -> training example
alignment.py character span <-> BIO (tokenizer-independent)
evaluate.py leak rate, per-entity metrics
inference.py the three stages combined
app/ interface and service
tools/ tools
run_tests.py all checks
check_taxonomy.py label contract
generate_dataset.py generate a training set
verify_tokenizer.py check tokenizer assumptions (needs transformers)
mess_synthetisch.py the reproducible measurement
build_release.py ONNX + INT8 (optional, not the shipping path)
paket/ AppImage, Flatpak, Docker
train.py training (needs torch + GPU)
tests/ 30 test files
specs/ full requirements catalog (German)
The order in taxonomy.yaml is a contract. Append new tags, never
insert, never reorder. Otherwise old checkpoints silently map to the
wrong labels.
The nomenclatures come from open government data. All of them require attribution; it belongs in the model card and in the app, not only here.
Source code MIT, shipped packages AGPL-3.0. Which applies when, and
why there are two, is explained in LIZENZ.md (German). In short: the
AppImage, the Flatpak and the Docker image carry PyQt6 (GPL-3) and
PyMuPDF (AGPL-3), the Windows installer PyMuPDF; a single MIT over
everything would have promised something the bundled parts do not allow.
The model weights are MIT.
The source code was written with Claude Code (Anthropic), under the
direction and responsibility of the maintainer. From 1.0.1 on, the
commits carry a Co-Authored-By: Claude line.