Gegenbuch als Docker-Setup, lokales Buch auf eigener Maschine
Betreiber-Entscheidung: Das Gegenbuch laeuft auf einer eigenen Maschine fuer Prod und Staging; ein externes Git-Repository entfaellt, das Buch liegt lokal. Die Trennung, auf die es ankommt, ist damit gegeben - wer OpenCRM uebernimmt, kommt nicht ans Buch. Richtung bewusst so herum: Das Gegenbuch holt ueber HTTPS mit einem Token, das nur audit:read kann. OpenCRM kennt weder Adresse noch Schluessel des Gegenbuchs. Kein SSH-Zugang zum CRM noetig. tools/audit-notary/ enthaelt jetzt Dockerfile, entrypoint.sh, docker-compose.yml und .env.example. Zwei Dienste (prod, staging) mit getrennten Verzeichnissen und Schluesseln, gesteuert ueber COMPOSE_PROFILES - dasselbe Muster wie beim Caddy-Profil im Hauptstack. Der Signaturschluessel wird beim ersten Start auf der Gegenbuch-Maschine erzeugt. Lokaler Betrieb ist jetzt ein vollwertiger Modus statt eines Testschalters. Die Erfolgsmeldung benennt bei jedem Lauf, was abgedeckt ist und was nicht - statt der frueheren pauschalen Formulierung "kein Manipulationsschutz", die im Einsatz auf eigener Maschine schlicht falsch war. Verifiziert mit echtem Docker-Build gegen eine CRM-Attrappe: Genesis ohne Bestaetigung -> Code 4; mit Bestaetigung Normalbetrieb exit 0; Eintrag veraendert -> Alarm exit 2; Eintraege geloescht -> Alarm exit 2; Siegel verschwunden -> Alarm. Behoben beim Bauen: useradd -u 1000 || true verschluckte, dass UID 1000 im Node-Image vergeben ist - der Container startete gar nicht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,38 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🐳 Gegenbuch als Docker-Setup, lokales Buch auf eigener Maschine** (2026-08-22)
|
||||
- Betreiber-Entscheidung: Das Gegenbuch laeuft auf einer **eigenen Maschine**
|
||||
fuer Prod und Staging; ein externes Git-Repository entfaellt, das Buch
|
||||
liegt lokal auf dieser Maschine. Die Trennung, auf die es ankommt, ist
|
||||
damit gegeben – wer OpenCRM uebernimmt, kommt nicht ans Buch.
|
||||
- Richtung bewusst so herum: Das Gegenbuch **holt** ueber HTTPS mit einem
|
||||
Token, das nur `audit:read` kann. OpenCRM kennt weder Adresse noch
|
||||
Schluessel des Gegenbuchs. Kein SSH-Zugang zum CRM noetig – das Token kann
|
||||
ausschliesslich Pruefwerte lesen.
|
||||
- Unterverzeichnis `tools/audit-notary/`: `Dockerfile`, `entrypoint.sh`,
|
||||
`docker-compose.yml`, `.env.example`. Zwei Dienste (`prod`, `staging`) mit
|
||||
getrennten Verzeichnissen und Schluesseln, gesteuert ueber
|
||||
`COMPOSE_PROFILES` – dasselbe Muster wie beim Caddy-Profil im Hauptstack.
|
||||
Der Signaturschluessel wird beim ersten Start auf der Gegenbuch-Maschine
|
||||
erzeugt und verlaesst sie nie.
|
||||
- **Lokaler Betrieb ist jetzt ein vollwertiger Modus**, kein Testschalter
|
||||
mehr. Die Erfolgsmeldung benennt bei jedem Lauf, was abgedeckt ist
|
||||
(„jemand veraendert Daten im CRM“) und was nicht („jemand uebernimmt diesen
|
||||
Rechner hier“) – statt der frueheren pauschalen Formulierung „kein
|
||||
Manipulationsschutz“, die im Einsatz auf eigener Maschine schlicht falsch
|
||||
war.
|
||||
- Jeder Durchlauf schreibt `status.txt` fuer die Ueberwachung; alles ausser
|
||||
`exit=0` gehoert angesehen.
|
||||
- Verifiziert mit echtem Docker-Build gegen eine CRM-Attrappe: Genesis ohne
|
||||
Bestaetigung → Code 4; mit Bestaetigung → Buch angelegt, Normalbetrieb
|
||||
exit 0; **Eintrag nachtraeglich veraendert → Alarm exit 2**; **Eintraege
|
||||
geloescht (maxId geschrumpft) → Alarm exit 2**; Siegel verschwunden →
|
||||
Alarm. Testartefakte und Image geloescht.
|
||||
- Behoben beim Bauen: `useradd -u 1000 || true` verschluckte, dass UID 1000
|
||||
im Node-Image bereits vergeben ist – der Container startete gar nicht.
|
||||
Jetzt laeuft er als der vorhandene Benutzer `node`.
|
||||
|
||||
- [x] **🧮 Gegenbuch: Pruefmodus schreibt nicht mehr, Widerspruch aufgeloest (Pentest R181)** (2026-08-18)
|
||||
- **R181-01 (LOW→MED)** – Der Pruefmodus sagte zu, nichts zu veraendern und
|
||||
kein Schreibrecht zu brauchen – und pushte trotzdem: `ankerNachziehen()`
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# ============================================================
|
||||
# Gegenbuch für OpenCRM
|
||||
# ============================================================
|
||||
# Diese Datei gehört auf den Gegenbuch-Rechner – NICHT auf den CRM-Server.
|
||||
#
|
||||
# Was hier passiert: Der Rechner holt regelmäßig einen kurzen Kontrollwert von
|
||||
# OpenCRM ab und schreibt ihn in ein Buch, das nur hier liegt. Wird später im
|
||||
# CRM etwas nachträglich verändert, widerspricht das dem Buch.
|
||||
#
|
||||
# Welche Bücher sollen laufen?
|
||||
COMPOSE_PROFILES=prod,staging
|
||||
|
||||
# Nur Kosmetik – steht in den Einträgen des Buchs.
|
||||
NOTAR_EMAIL=gegenbuch@example.de
|
||||
|
||||
# ---------------- Produktion ----------------
|
||||
# Von welcher OpenCRM-Instanz wird geholt?
|
||||
PROD_CRM_URL=https://crm.example.de
|
||||
|
||||
# Token eines Benutzers mit dem Recht audit:read – MEHR NICHT.
|
||||
# Damit lassen sich ausschließlich Prüfwerte lesen: keine Kundendaten, keine
|
||||
# Änderungen. Selbst wenn es abhandenkommt, ist damit nichts anzufangen.
|
||||
PROD_CRM_TOKEN=
|
||||
|
||||
# Wie oft geprüft wird (Sekunden). 3600 = stündlich.
|
||||
# Kürzer heißt: kleineres Zeitfenster, in dem eine Änderung unbemerkt bliebe.
|
||||
PROD_INTERVAL=3600
|
||||
|
||||
# Beim ALLERERSTEN Start einmalig auf true setzen, danach wieder leeren.
|
||||
# Grund: Die erste Eintragung legt fest, was als Ausgangszustand gilt – das
|
||||
# soll nicht versehentlich passieren.
|
||||
PROD_GENESIS_ACK=
|
||||
|
||||
# Normalerweise leer lassen. Nur nötig, wenn das Gedächtnis des Gegenbuchs
|
||||
# verlorenging (z. B. Verzeichnis gelöscht) und du geklärt hast, warum.
|
||||
PROD_ADOPT_ACK=
|
||||
|
||||
# Wo das Buch liegt. DIESES VERZEICHNIS GEHÖRT INS BACKUP.
|
||||
PROD_DIR=./data/prod
|
||||
|
||||
# ---------------- Test / Staging ----------------
|
||||
STAGING_CRM_URL=https://staging.example.de
|
||||
STAGING_CRM_TOKEN=
|
||||
STAGING_INTERVAL=3600
|
||||
STAGING_GENESIS_ACK=
|
||||
STAGING_ADOPT_ACK=
|
||||
STAGING_DIR=./data/staging
|
||||
@@ -0,0 +1,23 @@
|
||||
# Gegenbuch – läuft auf einem EIGENEN Rechner, nicht dort, wo OpenCRM läuft.
|
||||
#
|
||||
# Der Container holt lesend einen Kontrollwert vom CRM (HTTPS + Token mit
|
||||
# audit:read) und legt seine Einträge in einem Git-Repository auf DIESER
|
||||
# Maschine ab. Er braucht keinen SSH-Zugang zum CRM, keine Datenbank, keine
|
||||
# Shell dort. Umgekehrt kennt OpenCRM dieses Gegenbuch überhaupt nicht.
|
||||
FROM node:20-slim
|
||||
|
||||
# git für das Buch, openssh-client für die Signaturen (ssh-keygen).
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git openssh-client ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY notary.mjs /opt/notary/notary.mjs
|
||||
COPY entrypoint.sh /opt/notary/entrypoint.sh
|
||||
RUN chmod +x /opt/notary/entrypoint.sh
|
||||
|
||||
# Nicht als root laufen. Das Node-Image bringt bereits einen Benutzer mit
|
||||
# UID 1000 mit ("node") – der passt zu Bind-Mounts unter ./data/.
|
||||
USER node
|
||||
WORKDIR /gegenbuch
|
||||
|
||||
ENTRYPOINT ["/opt/notary/entrypoint.sh"]
|
||||
@@ -18,7 +18,77 @@ der erste – es sieht nach doppeltem Boden aus und ist keiner. Das CRM braucht
|
||||
für dieses Verfahren **gar nichts** zu wissen: Es liefert nur einen lesbaren
|
||||
Kontrollwert, der keine Geheimnisse enthält.
|
||||
|
||||
## Einrichten
|
||||
## Einrichten mit Docker (empfohlen)
|
||||
|
||||
Auf dem **Gegenbuch-Rechner** – nicht auf dem CRM-Server:
|
||||
|
||||
```bash
|
||||
git clone <dieses Repository> opencrm
|
||||
cd opencrm/tools/audit-notary
|
||||
cp .env.example .env
|
||||
# .env ausfüllen: CRM-Adresse und Token eintragen
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Beim ersten Start einmalig `PROD_GENESIS_ACK=true` setzen (und danach wieder
|
||||
leeren) – die erste Eintragung legt fest, was als Ausgangszustand gilt, und das
|
||||
soll nicht versehentlich passieren.
|
||||
|
||||
**Zwei Bücher auf einer Maschine** sind vorgesehen: `prod` und `staging` sind
|
||||
getrennte Dienste mit getrennten Verzeichnissen und getrennten Schlüsseln.
|
||||
Welche laufen, steuert `COMPOSE_PROFILES` in der `.env`.
|
||||
|
||||
### Wer redet mit wem
|
||||
|
||||
```
|
||||
Gegenbuch ──holt lesend──> OpenCRM (HTTPS, Token nur mit audit:read)
|
||||
OpenCRM ─────────────────> (kennt das Gegenbuch nicht)
|
||||
```
|
||||
|
||||
Das ist der eigentliche Schutz. Das Gegenbuch **holt** – es lässt sich nichts
|
||||
schicken. OpenCRM kennt weder Adresse noch Schlüssel des Gegenbuchs. Wer
|
||||
OpenCRM übernimmt, kommt hier nicht heran.
|
||||
|
||||
Das Token kann ausschließlich Prüfwerte lesen: keine Kundendaten, keine
|
||||
Änderungen. Selbst wenn es abhandenkommt, ist damit nichts anzufangen.
|
||||
|
||||
Der Signaturschlüssel wird beim ersten Start **auf dem Gegenbuch-Rechner
|
||||
erzeugt** und verlässt ihn nie.
|
||||
|
||||
### Was ins Backup gehört
|
||||
|
||||
Das Datenverzeichnis (`./data/prod` bzw. `./data/staging`). Darin liegen das
|
||||
Buch, der Schlüssel und der Beobachtungsspeicher. Geht es verloren, beginnt die
|
||||
Beobachtung von vorn – und der nächste Lauf sagt das ausdrücklich, statt „alles
|
||||
gut" zu melden.
|
||||
|
||||
### Überwachung
|
||||
|
||||
Jeder Durchlauf schreibt seinen Stand nach `data/<instanz>/status.txt`:
|
||||
|
||||
```
|
||||
2026-08-22T16:49:50+00:00 exit=0 in Ordnung
|
||||
```
|
||||
|
||||
**Alles außer `exit=0` gehört angesehen.** Wer eine Überwachung hat, greift
|
||||
diese Datei ab; wer keine hat, schaut regelmäßig mit `docker compose logs`
|
||||
hinein. Ein Alarm, den niemand liest, ist keiner.
|
||||
|
||||
### Was dieser Betrieb abdeckt – und was nicht
|
||||
|
||||
**Abgedeckt:** Jemand verändert oder löscht nachträglich Einträge im CRM –
|
||||
auch mit direktem Datenbankzugriff. Das widerspricht dem Gegenbuch und fällt
|
||||
beim nächsten Durchlauf auf.
|
||||
|
||||
**Nicht abgedeckt:** Jemand übernimmt den Gegenbuch-Rechner selbst. Dagegen
|
||||
hülfe nur eine zusätzliche Ablage außerhalb (z. B. ein privates Git-Repository
|
||||
auf einem dritten Server) – das ist vorbereitet, aber für die meisten
|
||||
Installationen mehr Aufwand als Nutzen. Die Erfolgsmeldung benennt diese Grenze
|
||||
bei jedem Lauf, damit sie nicht in Vergessenheit gerät.
|
||||
|
||||
---
|
||||
|
||||
## Einrichten ohne Docker
|
||||
|
||||
Auf einem **anderen** Rechner als dem CRM-Server:
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Gegenbuch für OpenCRM
|
||||
# =====================
|
||||
# Gehört auf einen EIGENEN Rechner – nicht dorthin, wo OpenCRM läuft.
|
||||
#
|
||||
# Richtung der Verbindung (das ist der eigentliche Schutz):
|
||||
# Gegenbuch ──holt lesend──> OpenCRM (HTTPS, Token nur mit audit:read)
|
||||
# OpenCRM ─────────────────> (kennt das Gegenbuch nicht)
|
||||
#
|
||||
# Wer OpenCRM übernimmt, kommt damit nicht an dieses Buch heran. Deshalb holt
|
||||
# das Gegenbuch selbst, statt sich etwas schicken zu lassen.
|
||||
#
|
||||
# Start:
|
||||
# cp .env.example .env # Werte eintragen
|
||||
# docker compose up -d
|
||||
#
|
||||
# Zwei Bücher auf einer Maschine sind vorgesehen (Produktion und Test): zwei
|
||||
# getrennte Dienste mit getrennten Verzeichnissen und getrennten Schlüsseln.
|
||||
# Welche laufen, steuert COMPOSE_PROFILES in der .env.
|
||||
|
||||
services:
|
||||
prod:
|
||||
build: .
|
||||
container_name: gegenbuch-prod
|
||||
restart: unless-stopped
|
||||
profiles: ["prod"]
|
||||
environment:
|
||||
INSTANZ: prod
|
||||
CRM_URL: ${PROD_CRM_URL}
|
||||
CRM_TOKEN: ${PROD_CRM_TOKEN}
|
||||
NOTARY_INTERVAL: ${PROD_INTERVAL:-3600}
|
||||
NOTAR_EMAIL: ${NOTAR_EMAIL:-gegenbuch@localhost}
|
||||
# Nur beim allerersten Lauf einmalig auf true, danach wieder leeren:
|
||||
NOTARY_GENESIS_ACK: ${PROD_GENESIS_ACK:-}
|
||||
# Nur nach geklärtem Verlust des Beobachtungsspeichers, siehe README:
|
||||
NOTARY_ADOPT_ACK: ${PROD_ADOPT_ACK:-}
|
||||
volumes:
|
||||
# Buch, Schlüssel, Beobachtungsspeicher und Statusdatei.
|
||||
# Dieses Verzeichnis ist das Gegenbuch – es gehört ins Backup.
|
||||
- ${PROD_DIR:-./data/prod}:/gegenbuch
|
||||
|
||||
staging:
|
||||
build: .
|
||||
container_name: gegenbuch-staging
|
||||
restart: unless-stopped
|
||||
profiles: ["staging"]
|
||||
environment:
|
||||
INSTANZ: staging
|
||||
CRM_URL: ${STAGING_CRM_URL}
|
||||
CRM_TOKEN: ${STAGING_CRM_TOKEN}
|
||||
NOTARY_INTERVAL: ${STAGING_INTERVAL:-3600}
|
||||
NOTAR_EMAIL: ${NOTAR_EMAIL:-gegenbuch@localhost}
|
||||
NOTARY_GENESIS_ACK: ${STAGING_GENESIS_ACK:-}
|
||||
NOTARY_ADOPT_ACK: ${STAGING_ADOPT_ACK:-}
|
||||
volumes:
|
||||
- ${STAGING_DIR:-./data/staging}:/gegenbuch
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# Gegenbuch-Schleife: einrichten, dann in festem Takt beglaubigen.
|
||||
set -uo pipefail
|
||||
|
||||
: "${INSTANZ:?INSTANZ fehlt (z. B. prod oder staging)}"
|
||||
: "${CRM_URL:?CRM_URL fehlt – von welcher OpenCRM-Instanz soll geholt werden?}"
|
||||
: "${CRM_TOKEN:?CRM_TOKEN fehlt – Token eines Benutzers mit audit:read}"
|
||||
|
||||
INTERVALL="${NOTARY_INTERVAL:-3600}"
|
||||
BUCH=/gegenbuch/buch
|
||||
SCHLUESSEL=/gegenbuch/schluessel
|
||||
export NOTARY_STATE_FILE=/gegenbuch/beobachtungen.jsonl
|
||||
export NOTARY_ALLOW_LOCAL=true # Buch liegt auf dieser Maschine
|
||||
export NOTARY_PUSH=false # es gibt kein Ziel zum Pushen
|
||||
|
||||
echo "[$INSTANZ] Gegenbuch startet – Quelle: $CRM_URL, Takt: ${INTERVALL}s"
|
||||
|
||||
# --- Signaturschlüssel: einmalig auf DIESER Maschine erzeugen ---------------
|
||||
# Er verlässt den Rechner nie. OpenCRM kennt ihn nicht.
|
||||
mkdir -p "$SCHLUESSEL"
|
||||
if [ ! -f "$SCHLUESSEL/id_ed25519" ]; then
|
||||
echo "[$INSTANZ] Erzeuge Signaturschlüssel..."
|
||||
ssh-keygen -t ed25519 -f "$SCHLUESSEL/id_ed25519" -N "" -C "gegenbuch-$INSTANZ" -q
|
||||
fi
|
||||
chmod 600 "$SCHLUESSEL/id_ed25519"
|
||||
if [ ! -f "$SCHLUESSEL/allowed_signers" ]; then
|
||||
echo "gegenbuch-$INSTANZ namespaces=\"git\" $(cat "$SCHLUESSEL/id_ed25519.pub")" \
|
||||
> "$SCHLUESSEL/allowed_signers"
|
||||
fi
|
||||
|
||||
# --- Buch anlegen, falls noch nicht vorhanden ------------------------------
|
||||
if [ ! -d "$BUCH/.git" ]; then
|
||||
echo "[$INSTANZ] Lege Gegenbuch an..."
|
||||
mkdir -p "$BUCH"
|
||||
git -C "$BUCH" init -q -b main
|
||||
fi
|
||||
cd "$BUCH" || exit 1
|
||||
|
||||
git config user.email "${NOTAR_EMAIL:-gegenbuch@localhost}"
|
||||
git config user.name "Gegenbuch $INSTANZ"
|
||||
git config gpg.format ssh
|
||||
git config user.signingkey "$SCHLUESSEL/id_ed25519.pub"
|
||||
git config gpg.ssh.allowedSignersFile "$SCHLUESSEL/allowed_signers"
|
||||
|
||||
# Ein erster Commit, damit es eine Historie gibt, an die angehängt werden kann.
|
||||
if ! git rev-parse HEAD >/dev/null 2>&1; then
|
||||
git commit -q -S --allow-empty -m "Gegenbuch $INSTANZ angelegt"
|
||||
fi
|
||||
|
||||
# --- Schleife ---------------------------------------------------------------
|
||||
# Jeder Rückgabecode außer 0 gehört angesehen. Der letzte Stand steht
|
||||
# zusätzlich in status.txt, damit eine Überwachung ihn abgreifen kann, ohne
|
||||
# Logs zu durchsuchen.
|
||||
while true; do
|
||||
ZEIT=$(date -Iseconds)
|
||||
node /opt/notary/notary.mjs
|
||||
CODE=$?
|
||||
case $CODE in
|
||||
0) LAGE="in Ordnung" ;;
|
||||
1) LAGE="Betriebsfehler (Konfiguration, CRM nicht erreichbar, Commit)" ;;
|
||||
2) LAGE="BEFUND – Widerspruch zwischen CRM und Gegenbuch" ;;
|
||||
3) LAGE="nicht abschliessend feststellbar (u. a. erster Lauf)" ;;
|
||||
4) LAGE="Gedaechtnis fehlt – Bestaetigung noetig, siehe README" ;;
|
||||
5) LAGE="Anker unvollstaendig" ;;
|
||||
*) LAGE="unbekannter Code" ;;
|
||||
esac
|
||||
echo "[$INSTANZ] $ZEIT exit=$CODE ($LAGE)"
|
||||
printf '%s exit=%s %s\n' "$ZEIT" "$CODE" "$LAGE" > /gegenbuch/status.txt
|
||||
sleep "$INTERVALL"
|
||||
done
|
||||
@@ -221,14 +221,22 @@ async function hole(pfad) {
|
||||
return j.data;
|
||||
}
|
||||
|
||||
// Lokaler Betrieb ist ein vollwertiger Modus, wenn das Gegenbuch auf einem
|
||||
// EIGENEN Rechner laeuft: Wer das CRM uebernimmt, kommt dann nicht an dieses
|
||||
// Buch. Was fehlt, ist der Schutz gegen jemanden, der DIESEN Rechner
|
||||
// uebernimmt – das gehoert benannt, aber es ist kein „kein Schutz“.
|
||||
const OHNE_ANKER = () =>
|
||||
NUR_LOKAL ? ' – ACHTUNG: nur lokal, kein externer Anker, also kein Manipulationsschutz.' : '';
|
||||
NUR_LOKAL
|
||||
? '\n Betriebsart: lokal. Geschützt ist damit der Fall „jemand verändert Daten im ' +
|
||||
'CRM“.\n NICHT geschützt ist der Fall „jemand übernimmt diesen Rechner hier“ – ' +
|
||||
'dagegen hülfe\n nur eine zusätzliche Ablage außerhalb.'
|
||||
: '';
|
||||
|
||||
// Ohne bekannte Mindesthoehe bleibt ein Rewind auf einen aelteren, echt
|
||||
// signierten Stand unerkennbar (R178-01). Das gehoert in die Erfolgsmeldung,
|
||||
// sonst liest sich „OK“ als mehr Gewissheit, als vorhanden ist.
|
||||
const REWIND_VORBEHALT = () =>
|
||||
REWIND_GESCHUETZT || ffBelegt
|
||||
REWIND_GESCHUETZT || ffBelegt || NUR_LOKAL
|
||||
? ''
|
||||
: '\n Offen: Ein Zurückspulen der Reihe auf einen älteren, ebenfalls signierten Stand ' +
|
||||
'lässt sich hier nicht ausschließen.' +
|
||||
@@ -251,6 +259,11 @@ const REWIND_VORBEHALT = () =>
|
||||
// bewusst NICHT mit dem Manipulationsalarm (2) verschmolzen.
|
||||
const abschliessendSicher = (anzahl) =>
|
||||
REWIND_GESCHUETZT ||
|
||||
// Im lokalen Betrieb gibt es keinen Remote, den jemand zurueckspulen koennte.
|
||||
// Der Vergleich gegen den eigenen Beobachtungsspeicher ist hier der
|
||||
// vollstaendige Nachweis – die Grenze liegt bei der Integritaet dieses
|
||||
// Rechners und ist als solche benannt, nicht ungeklaert.
|
||||
(NUR_LOKAL && ffBelegt) ||
|
||||
// BEWUSST NICHT `ankerBelegt` (Pentest R180-01): Die Anker-Refs zeigen nur
|
||||
// den HOECHSTEN NOCH VORHANDENEN Stand – nicht den hoechsten je erreichten.
|
||||
// Wer die obersten Refs loescht, senkt damit den Vergleichswert selbst; ein
|
||||
@@ -305,8 +318,9 @@ if (hatRemote) {
|
||||
process.exit(3);
|
||||
}
|
||||
} else if (process.env.NOTARY_ALLOW_LOCAL === 'true') {
|
||||
// Der Schalter darf nicht zu einem glatten „OK“ fuehren: Leckt er je ins
|
||||
// Produktivumfeld, gaebe es gruenes Licht auf nicht notarisiertem Stand.
|
||||
// Ausdruecklich gewaehlter lokaler Betrieb (Gegenbuch auf eigenem Rechner,
|
||||
// ohne zusaetzliche Ablage ausserhalb). Die Erfolgsmeldung benennt, was das
|
||||
// abdeckt und was nicht – ein glattes „OK“ ohne Einordnung gibt es nicht.
|
||||
NUR_LOKAL = true;
|
||||
} else {
|
||||
console.error(
|
||||
|
||||
Reference in New Issue
Block a user