Harden source release preparation and record verified security follow-up
Dependency Audit / dependency-audit (push) Successful in 1m51s
Deployment Installer / deployment-installer (push) Successful in 8s
Security Audit / security-audit (push) Successful in 12m32s

This commit is contained in:
2026-09-08 08:04:12 +02:00
parent 9554657bb5
commit 58d320d9b3
26 changed files with 3833 additions and 163 deletions
@@ -361,6 +361,15 @@ and WebUI API proxy traffic across API replicas. The WebUI and API services do
not publish host ports. HAProxy has no Docker socket and discovers only the
bounded replica slots rendered into `load-balancer.cfg`.
New installation specifications default to HAProxy `3.2.23-alpine`, pinned to
registry index `sha256:6343ce34a132a5dceaa24767d739df2bd519f8f7c1079ae39e4821334e8eb42e`.
This patch remains on HAProxy 3.2 LTS and Alpine 3.24.1. Loading an existing
specification preserves its explicit image; it does not perform an upgrade.
The [runtime remediation evidence](../security/RUNTIME_IMAGE_REMEDIATION_2026-09-08.md)
records both architecture scans and the pending binary/configuration, runtime,
inventory and final-image checks. The source default is not a release approval:
runtime publication remains held in Meta #52.
Replica counts are desired state:
```sh
@@ -263,11 +263,198 @@ python tools/release/generate-developer-meta-package.py
python tools/release/generate-developer-meta-package.py --check
```
The direct generator is a development synchronization tool, not a receipt-gated
release executor. For release preparation, use the guarded out-of-run stage below.
`push-release-tag.sh` performs this synchronization before release commits and
tags. The meta-package is for editable/developer setup and composition tests. It
does not enable modules, apply migrations, provision services, or establish
backup and recovery evidence.
### Shared source-tag contract and Meta composition
The shared version collector names Meta's real
`packages/govoplan-meta/pyproject.toml` separately from root `pyproject.toml`.
Only the registered `govoplan` system/meta repository with nested project name
`govoplan` receives this contract. Missing, unknown, or misidentified metadata
does not become a versionless exception. Version alignment compares the complete
nested file with the canonical operator-tool generator output: its version must
match Core, and dependencies and `full` composition must match the reviewed
requirements and workspace package versions. Validation never executes a
generator from a selected checkout. The shared trusted manifest checker can
load reviewed application manifests; these checks are not a code sandbox.
Meta's complete generated file is recognized by shared version-mutation discovery,
but the generic durable version executor deliberately cannot write it. A durable
run freezes the release console's own Meta checkout as trusted runtime code;
changing it in place would invalidate that run. The planner therefore places Meta
after Core and exposes only non-executable support preparation/publication steps,
not misleading automatic Meta version, commit, tag, or push actions. A missing or
different Core target produces an actionable preparation prerequisite.
Prepare Core and the intended module inputs first, commit their reviewed state,
then stop active durable runs for the target workspace. Use trusted operator tools
against a separate registered, private source checkout, never the running operator
Meta directory. Preview outside any selected source checkout, for example:
```sh
python tools/release/prepare-developer-meta-package.py \
--workspace /private/release-workspace --target-version X.Y.Z \
> /private/operator/meta-preview.json
python tools/release/prepare-developer-meta-package.py \
--workspace /private/release-workspace --target-version X.Y.Z \
--receipt /private/operator/meta-preview.json --apply --confirm-out-of-run
```
The explicit confirmation attests that no durable run is active for that target
workspace; the helper does not discover or stop other processes. Preview/apply
requires registered clean main sources, matching origins and live-main ancestry,
Core already aligned at the target, the exact nested identity, and no existing or
unverifiable target Meta tag. Frozen receipts cover source HEADs/filesystem
identities, release requirements, every discovered registered full-composition
pyproject, the trusted generator snapshot, and the resulting full-file hash.
Inputs are limited to 128 selected files, 2 MiB per file and 16 MiB aggregate.
The canonical generator renders copied bounded data in a temporary directory;
no generator from the selected checkout executes. Changed receipts block before
the file effect. Apply writes only `packages/govoplan-meta/pyproject.toml`, then
rechecks the other sources and exact output. A write or post-check failure that
may have changed the file reports `needs-reconciliation` and leaves that bounded
delta for explicit review; it never retries, rolls back, commits or publishes.
Review the complete generated composition and manually commit the resulting file.
Complete matching Core publication before guarded Meta source tagging/publication,
then start a fresh durable run from reviewed, clean, published operator tooling.
Hot self-updating durable Meta release execution remains explicitly unsupported;
this out-of-run preparation is the existing developer-meta support contract.
For every `tag_repositories` batch, strict checks apply to every selected
repository before any tag creation, fetch, or push: registered checkout and
origin/push URL, a clean `main` tracking `origin/main`, live remote-main ancestry,
exact frozen HEADs, and immutable annotated local/remote tag objects. Git metadata
must remain inside the operator's trusted workspace. Missing local knowledge of
live remote main is a blocker; fetch and review it separately. Unknown repositories
and non-registered remote aliases fail closed. If selected, Meta runs last.
For Meta only, the matching annotated Core release must exist before its effect; Core may be
an earlier selected repository, or an already tagged dependency. Publication
requires that Core's exact tag and main commit are already remote.
Non-Meta selections do not acquire Meta's composition or Core-tag prerequisite.
Local module-candidate tags still work before Core's final release lock or tag.
The existing Core WebUI bundle gate still applies to module publication and
batches selecting Core: relevant Core release-package and release-lock inputs
must be operator-owned regular files, at most 16 MiB each, and their identities
and content hashes are frozen before preflight and rechecked before every effect.
When Core is unselected, this does not require its checkout to be clean or tagged;
reviewed pending composition inputs retain their previous meaning. Backend-only
selections never read irrelevant Core WebUI files.
Before even read-only Git commands, source ancestry must be owned by root or the
current operator and must not be group/world writable. A sticky shared ancestor
such as `/tmp` is permitted only above an owned, protected child; the workspace
and checkouts receive no writable-directory exception. The current operator must
own source inputs and actual Git/worktree/common metadata, which must be regular
files/directories, non-symlinked, and non-writable by other users. Metadata walks
are bounded to 500,000 entries and 128 levels, and tracked inputs to 100,000 paths
and 16 MiB of listing text. Read-only Git targets, object alternates/grafts and
hidden/sparse/unmerged index entries are blocked. Frozen receipts include actual
checkout/Git directory paths, devices, inodes, owners and modes, so replacing Git
metadata with the same HEAD is still detected. All selected version/composition
inputs must be tracked, including root and WebUI package/lock metadata, discovered
module manifests and package initializers, and Meta's nested package and release
requirements; ignored working files cannot supply declarations absent from a tag.
No chmod, ownership repair or
global Git trust change is performed. A shared writable workspace must first be
recreated or reviewed in the operator's protected release area by an explicitly
authorized preparation workflow.
Preview is read-only. Local-tag mode creates only pinned annotated tags (or
retrieves an identical published annotation); it does not publish main or tags.
Publish mode atomically pushes the frozen main commit and annotation object,
without force, retagging, fallback, or automatic retry. The complete source
receipt is rechecked before every effect and afterward; remote main and the
exact annotated tag must both match, not merely the Git exit status. Changes
after preflight stop the remaining batch. Atomicity is per repository, not
across repositories: earlier successful publications and a newly created local
tag can remain after a later failure. Inspect reported receipts and obtain a new
review before retrying; do not move immutable tags.
Whole-batch revalidation deliberately repeats source and live-remote checks around
each repository effect; the number of checks can grow quadratically with batch
size. Plan release time accordingly rather than bypassing trust checks. The
shared internal preflight is read-only and exposes no legacy mutation path.
The fixture suite covers Meta and non-Meta preview/local-tag/
publication using temporary local bare remotes, including stale compositions,
unsafe origins, divergent branches, damaged tag identity, changed receipts and
false publication success. This is local tooling evidence, not a real release
publication or production permission check.
Deutsch: Die gemeinsamen Helfer erkennen ausschließlich das registrierte
Meta-Repository mit dem echten Paket `packages/govoplan-meta/pyproject.toml`
(Projektname `govoplan`). Version und vollständige Zusammensetzung müssen dem
kanonischen Generator, Core und den geprüften Anforderungen entsprechen; der
Generator stammt niemals aus dem ausgewählten Checkout. Der gemeinsame
Manifestprüfer kann geprüften Anwendungscode laden und ist keine Sandbox.
Die gemeinsame Änderungsplanung erkennt die vollständig generierte Paketdatei,
aber der dauerhafte Versionsausführer darf Meta nicht selbst verändern: sein
eingefrorener Lauf bindet den Meta-Checkout als vertrauenswürdigen Programmstand.
Meta erscheint deshalb nach Core ausschließlich mit nicht automatisch ausführbaren
Vorbereitungs-/Veröffentlichungsschritten. Zuerst Core und Modulquellen vorbereiten
und geprüft committen; bei abweichender Core-Zielversion nennt der Plan diese
Voraussetzung ausdrücklich. Aktive dauerhafte Läufe des Ziel-Workspaces beenden.
Mit `prepare-developer-meta-package.py` zunächst eine Vorschau außerhalb der
Quell-Checkouts speichern, dann deren JSON über `--receipt` zusammen mit `--apply`
und `--confirm-out-of-run` bestätigen. Das Ziel muss ein separater registrierter
privater Checkout sein, niemals das laufende Operator-Meta. Die Bestätigung ist
eine Betreibererklärung; der Helfer sucht oder beendet keine fremden Prozesse.
Quell-HEADs, Pfadidentitäten, Anforderungen, alle registrierten vollständigen
Paket-Eingaben, der vertrauenswürdige Generator und der vollständige Ausgabehash
werden eingefroren. Es gelten höchstens 128 Quelldateien, 2 MiB je Datei und
16 MiB insgesamt. Core muss bereits vollständig zur Zielversion passen; vorhandene
oder nicht verifizierbare Meta-Zieltags sperren die Vorbereitung. Geänderte
Nachweise stoppen vor dem Schreiben. Ausschließlich die verschachtelte Paketdatei
wird vollständig generiert und danach geprüft; ein Fehler nach dem Schreiben
meldet `needs-reconciliation` und erfordert die manuelle Prüfung dieser begrenzten
Änderung, ohne automatisches Zurücksetzen. Kein automatischer
Commit, Push oder Wiederholungsversuch findet statt. Zusammensetzung prüfen,
manuell committen, Core zuerst veröffentlichen, dann die geschützte Meta-Tag-Route
verwenden und einen neuen dauerhaften Lauf starten. Eine Selbstaktualisierung
des aktiven dauerhaften Meta-Laufs bleibt ausdrücklich nicht unterstützt.
Für jeden Tag-Stapel, auch ohne Meta, gelten
Vertrauens-, Origin-, saubere Main- und Live-Abstammungsprüfungen für die gesamte
Auswahl vor jeder Änderung. Unbekannte Repositories und nicht registrierte
Remote-Aliase sind gesperrt. Nur bei ausgewähltem Meta gelten zusätzlich dessen
Zusammensetzungsprüfung und der passende annotierte Core-Tag als Voraussetzung;
Meta folgt zuletzt. Für Metas Veröffentlichung müssen Core-Tag und Main-Commit
bereits auf dem Remote vorliegen. Lokale Modul-Kandidatentags bleiben vor Cores
abschließendem Release-Lock und Tag möglich. Die vorhandene Core-WebUI-Bundleprüfung
bleibt bei Modulveröffentlichung und Core-Auswahl erhalten. Relevante Core-Paket-
und Lockdateien müssen eigene reguläre Dateien mit höchstens je 16 MiB sein;
Identität und Inhaltshash werden eingefroren und vor jeder Aktion erneut geprüft.
Nicht ausgewähltes Core benötigt dafür weder einen sauberen Checkout noch einen
Tag. Reine Backend-Auswahlen lesen keine irrelevanten Core-WebUI-Dateien.
Vor Git-Aufrufen werden Eigentümer, Schreibrechte, sichere
Pfadabstammung und echte Git-/Worktree-Metadaten geprüft; veränderbare gemeinsame
Verzeichnisse, fremde Eigentümer, Alternates, Grafts und versteckte Indexeinträge
sind gesperrt. Ein Sticky-Bit-Vorfahr wie `/tmp` ist nur oberhalb eines eigenen
geschützten Unterverzeichnisses zulässig. Es erfolgen weder Rechtereparaturen
noch globale Git-Vertrauensänderungen. Ausgewählte Versions- und Zusammensetzungs-
dateien müssen versioniert sein: Paket-/Lockdateien, Modulmanifeste und
Paketinitialisierer sowie Metas verschachteltes Paket und Release-Anforderungen.
Ignorierte Arbeitsdateien dürfen keine vom Tag abweichenden Angaben liefern.
Die Vorschau schreibt nichts, lokale Tags veröffentlichen nichts,
und die Veröffentlichung überträgt Main und den exakten annotierten Tag atomar
je Repository. Unmittelbar vor und nach den Aktionen werden die eingefrorenen
Quellnachweise erneut geprüft, einschließlich entferntem Main und Tag-Objekt.
Bei Änderungen oder Fehlern stoppt der Rest des Stapels ohne automatischen
Wiederholungsversuch. Frühere Veröffentlichungen und neu erzeugte lokale Tags
können bestehen bleiben: vor einem neuen Versuch Nachweise prüfen und erneut
freigeben, niemals unveränderliche Tags verschieben. Die vollständigen Quell- und
Live-Remote-Prüfungen werden um jede Aktion wiederholt; bei großen Stapeln kann
deren Anzahl quadratisch wachsen. Diese konservativen Prüfkosten gehören zur
Release-Planung. Der interne Vorprüfer ist ausschließlich lesend und besitzt
keinen alten Änderungspfad. Tests für Auswahlen mit und ohne Meta verwenden
nur temporäre lokale Remotes und ersetzen keine echte Veröffentlichungsprüfung.
If the tag-triggered developer meta-package job fails before publication, rerun
`publish-developer-meta-package.yml` with the existing protected version. The
manual path validates that tag against `main`, checks out its exact commit, and
@@ -0,0 +1,139 @@
# Runtime image remediation follow-up — 8 September 2026
Canonical tracking: [Meta #52](https://git.add-ideas.de/GovOPlaN/govoplan/issues/52)
and [website #9](https://git.add-ideas.de/add-ideas/addideas-govoplan-website/issues/9).
This addendum supplements the [original audit](RUNTIME_IMAGE_AUDIT_2026-09-08.md);
it does not replace that historical baseline or lift either publication or
deployment gate. The immutable 0.1.45 release and `catalog-v0.1.45` are unchanged.
## Source change and candidate decisions
New installer specifications now use
`haproxy:3.2.23-alpine@sha256:6343ce34a132a5dceaa24767d739df2bd519f8f7c1079ae39e4821334e8eb42e`.
This is a patch update from 3.2.21 within the supported
[3.2 LTS branch](https://www.haproxy.org/), keeping Alpine 3.24.1.
The [publisher's exact build source](https://github.com/docker-library/haproxy/blob/7a5c202cde713867a737033dca56e7a211a8b8df/3.2/alpine/Dockerfile)
and scanned image configuration retain the non-root `haproxy` user,
`/usr/local/etc/haproxy/haproxy.cfg`, entrypoint and graceful-stop signal.
The [upstream changelog](https://www.haproxy.org/download/3.2/src/CHANGELOG)
includes HTTP parsing, TLS and memory-safety fixes in 3.2.22/3.2.23.
Existing specifications retain their explicit image, including an older pin;
this source change does not update a running installation.
| Candidate | OS | C / H / M / L / Unknown, per architecture | Disposition |
| --- | --- | --- | --- |
| HAProxy `3.2.23-alpine` | Alpine 3.24.1 | 0 / 0 / 0 / 0 / 0 | Installer source default updated; binary/runtime checks pending |
| nginx-unprivileged `1.30.4-alpine` | Alpine 3.24.1 | 0 / 0 / 0 / 0 / 0 | Candidate only; compatibility and website OS upgrade review pending |
| Caddy `2.11.4-alpine` | Alpine 3.23.5 | 1 / 38 / 41 / 12 / 23 | Not selected; all 39 C/H records have recorded fixes |
| Node `24-alpine` (24.20.0) | Alpine 3.24.1 | 0 / 6 / 11 / 12 / 0 | Not selected; major change and six fixable HIGH records |
Each row was scanned separately for **linux/amd64 and linux/arm64**, with the
same counts on both. Counts are package-vulnerability records, not distinct
CVEs or proven exploits. The [machine-readable evidence](runtime-image-candidates-2026-09-08.json)
contains exact index, platform-manifest, config and report digests, inventory
counts, scanner bounds and decisions. It is audit data, not an accepted release
manifest or an installer input.
nginx's candidate reference is
`docker.io/nginxinc/nginx-unprivileged:1.30.4-alpine@sha256:442753882674b49ae2c1de83ed67896131c0777f56df5005e356e62bc3f7e7ce`.
It inventories 70 Alpine packages including nginx/NJS, uses UID 101 and exposes
8080. The [upstream stable release](https://nginx.org/en/download.html) and
[security advisories](https://nginx.org/en/security_advisories.html) include the
1.30.4 fixes. The publisher retains its
[unprivileged port and temporary-path contract](https://github.com/nginx/docker-nginx-unprivileged).
For the website, this changes nginx 1.27.5 to 1.30.4, NJS 0.8.10 to 1.0.1 and
Alpine 3.21.3 to 3.24.1. These changes are explicit review items; the website
Dockerfile has not been changed. The GovOPlaN Web image still requires an
explicit verified `NGINX_IMAGE` build argument.
Caddy's candidate reference is
`docker.io/library/caddy:2.11.4-alpine@sha256:5f5c8640aae01df9654968d946d8f1a56c497f1dd5c5cda4cf95ab7c14d58648`.
Although this is the current
[official image line](https://raw.githubusercontent.com/docker-library/official-images/master/library/caddy),
its inventory still includes Go 1.26.3, `x/crypto` 0.52.0, `x/net` 0.55.0,
`x/text` 0.37.0 and gRPC 1.81.0. Recorded fixes include Go 1.26.6,
`x/crypto` 0.55.0, `x/net` 0.56.0, `x/text` 0.39.0 and gRPC 1.83.1; Alpine
findings also remain in c-ares, curl/libcurl and OpenSSL. The CRITICAL
`CVE-2026-56854` concerns `x/crypto/ssh` source-address enforcement. A module
record alone does not establish that this binary exposes that SSH path; exact
binary symbol/reachability analysis is still required for a disposition.
The [official Node image catalogue](https://raw.githubusercontent.com/docker-library/official-images/master/library/node)
still maps Node 22 Alpine to 22.23.2 and the previously scanned digest. Node 24's
candidate is
`docker.io/library/node:24-alpine@sha256:e67514e5d0f6c46656005e1b693b2ec9d52e80b641307de684d4a015ba7a4eaf`.
Its HIGH records remain in two OpenSSL packages and npm dependencies
`brace-expansion`, `ip-address` and `tar`; fixing the earlier critical tar
record alone is insufficient. The website builder stays on Node 22 pending
a reviewed build-tool remedy and a final builder scan.
## Method, verification and retained evidence
The existing Trivy 0.74.0 executable was rehashed against the previously verified
archive member: `d89bcc6510a267f11b773398cbf1be5520ce39f9e8b6633178c4487f05b7d791`.
The same schema-2 vulnerability database was used, updated
`2026-09-07T19:06:01.154199452Z`. No tool installation or database refresh occurred.
Index bytes matched both the registry digest header and Docker Hub publisher
metadata; both platform-manifest byte hashes matched the index. All eight
registry-only scans completed successfully with validated JSON, `--list-all-pkgs`,
`--scanners vuln`, an eight-minute/2GB image bound, an empty Docker configuration
and no inherited credentials. Exit zero means execution succeeded. Private
temporary paths and in-memory artifact cache isolated this follow-up from the
earlier scanner's artifact cache; its vulnerability database was read only.
Raw reports, logs, manifests, publisher metadata and the scanner script are in
`/home/zemion/.cache/govoplan-runtime-remediation.qfDSWHJh/`:
- `summary.json` SHA-256: `0d44390408ab35270e4430516f77bf11aa7877334eff2ef19e11e9a863fe5c56`.
- `frozen-images.json` SHA-256: `d0cb156f4a88998531ec55ab950067a3f1350ded648f07650c463af101dad467`.
- `scan_successors.py` SHA-256: `f64c69e06efc2ad7b5a657a25aa73f9ff586e3685737d525456bcecbe3ab5f07`.
Local retention is not permanent artifact hosting; preserve this evidence with
the eventual reviewed release. The JSON evidence records compressed registry
layer sizes; these are not expanded filesystem limits or final GovOPlaN sizes.
Installer regression checks cover the new generated image pin, legacy
specification fallback, preserved explicit images, generated topology and
configuration: `python -I -m unittest discover -s tests -p
test_deployment_installer.py` ran 45 tests successfully with one skip because
Core was not importable in that isolated test environment. The skipped Core
startup-configuration integration was subsequently rerun in the shared development
environment with Core available: all 45 installer tests passed with no skips,
including generated-environment startup validation. This is configuration
validation, not execution of the candidate image.
Both repositories passed `git diff --check`; the audit JSON and all eight
report hashes were checked against the retained evidence.
**Docker, Podman and HAProxy executables are unavailable on
this host**, so no image or HAProxy configuration was executed and no daemon was
installed. Publisher metadata and installer tests support the scoped source
patch; they do not establish binary or deployed compatibility.
## Gates that remain open
- Validate `haproxy -c` on generated local, existing-proxy and managed-ingress
configurations using the exact pinned image and target architectures. Run
bounded isolated checks without live mounts, secrets, privilege or external
network access. Then verify DNS discovery, readiness, forwarded headers,
replica routing and graceful termination in the intended runtime.
- Test the nginx candidate with both the website configuration and GovOPlaN
WebUI entrypoint/proxy configuration, including UID 101, writable temporary
paths, health paths, cache headers and static catalog bytes. Approve the
website nginx/NJS/Alpine version changes before changing its Dockerfile.
- Resolve Caddy, Node build-tool and all unchanged baseline dependencies with
updated publisher images or narrow reviewed applicability evidence. No
severity-wide exceptions or custom third-party rebuilds were introduced.
- Close the original source-built/static inventory gaps. HAProxy's 24-package
OS inventory still omits the source-built HAProxy executable. Node's npm
inventory still omits the Node executable/stdlib. Garage, CPython, Redis,
PostgreSQL and QEMU gaps are unchanged. Alpine 3.24 EOL metadata is still
missing from this scanner; zero findings is not complete coverage.
- Scan **final built** API/Web/website layers and the selected managed
dependencies on both architectures, then perform migration, worker,
readiness and ingress smoke checks. Record failure and unknown states.
Secrets, misconfiguration and image signature policy need separate checks.
- Obtain the website deployment host/operator and rebuild/restart authority,
preserving the exact immutable catalog/keyring/module-directory bytes and
verifying fresh public responses after an authorized rollout.
No images were built, executed, published or deployed; no running service,
release tag, signed manifest, CI image input or live infrastructure was changed.
@@ -0,0 +1,219 @@
# Security and performance follow-up — 8 September 2026
This follows the [original review](SECURITY_PERFORMANCE_REVIEW_2026-09-08.md)
and its post-release issue reconciliation. It describes new source work after
the frozen 0.1.45 release; it does not change published tags, packages, signed
catalogs or deployed images. Gitea remains the canonical state log.
## Implemented source slices
- [Core #297](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/297):
a shared disposable-process runner enforces wall/CPU/address-space/input/output
limits, bounded stderr, process-group cleanup and non-queuing per-process
admission. A private binary codec bounds decoding before allocating a full
object graph and preserves explicitly supported data types without pickle.
Read the owning Core `docs/BOUNDED_PROCESS_CONTRACT.md` before adding callers.
- Connectors XLSX parsing, Templates rendering, Files ZIP/TAR inspection and
extraction, and Dataflow reference previews/development execution now use
that boundary. Existing authorization, sessions, provider credentials,
idempotency and persistence remain in the parent. No unprotected inline
fallback is used. Each module contributes static EN/DE user/admin limits and
operational consequences through its manifest.
- Files snapshots authorized sources inside shared admission, validates private
staged members, and acknowledges each persisted member before decoding the
next. Numeric progress remains available. The acknowledgement is event-driven,
not a fixed sleep per member. Reads allocate by validated actual file size,
not by the configured ceiling. Failures reap children, clear private staging
and retain the existing transaction/blob cleanup and explicit retry behavior.
- Dataflow's normal reference preview formerly bypassed the backend wrapper;
it now enters the worker too. Nested source configurations cannot collide
merely because subflows reuse node IDs. Combined reference-source data is
checked before creating further columnar copies, while individual providers
retain their own authorized-read bounds. Staging/production still require
DuckDB; this change does not replace that separate backend.
- [Access #22](https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/22):
current-password change, session/CSRF rotation, cross-tenant session and human
API-key revocation, and optional administrator-assisted recovery. Recovery
codes are hashed, single-use, expire after 15 minutes, require a current local
System owner and explicit identity verification, and recheck current account,
membership, tenant and issuer authority at redemption. A password change also
invalidates outstanding codes issued by that account for other people. Audit
evidence and validation/error responses do not contain passwords or codes.
External-provider and service-account rules remain separate.
- The Access UI provides first-login/required change, self-service change,
policy-aware sign-in help, public code redemption and eligible owner issuance.
Core consumes an optional lazy auth-action capability rather than importing
Access internals. The required-action gate fails closed if its UI is missing.
- [Workflow Engine #3](https://git.add-ideas.de/GovOPlaN/govoplan-workflow-engine/issues/3):
full-history lists batch pinned revisions, while new summary and bounded
step/event endpoints preserve authorization and explicit pagination. Existing
full-history responses are not silently truncated. Exact inbox total semantics
are retained and their counting cost is documented.
- [Meta #55](https://git.add-ideas.de/GovOPlaN/govoplan/issues/55):
shared version/planning helpers recognize the existing nested developer
package, not invented root metadata. All tag batches enforce trusted private
source ownership, registered origins and clean main/upstream state. Meta
batches additionally require exact composition and matching Core evidence.
Whole-batch preflight,
frozen source receipts, annotated immutable tags, object-pinned atomic
publication and post-effect remote checks are covered with temporary local
repositories. Hidden Git index flags, unsafe ancestry, alternates and changed
Git-directory identities are rejected. Selected version/composition metadata
must be tracked, so ignored files cannot describe bytes absent from a tag.
Applicable unselected Core WebUI inputs have bounded, frozen read receipts;
backend-only releases do not read them. The existing local module-candidate
exception remains intact. The weaker legacy mutation path was removed.
Canonical whole-package preview and receipt-bound apply now cover Meta's
version preparation too. Core must already match the target. Preparation
requires a separate trusted checkout, explicit out-of-run confirmation and
unchanged source/tooling receipts; it cannot rewrite the running operator.
Plans place Meta after Core and explain the manual preparation/publication
steps instead of promising a durable self-update. Ambiguous partial writes
require reconciliation, without automatic rollback or retry.
- [Runtime-image follow-up](RUNTIME_IMAGE_REMEDIATION_2026-09-08.md): eight new
registry-only scans cover four exact candidates on amd64 and arm64. New
installer specifications select the patched same-line HAProxy digest;
existing specifications retain their explicit image. Other candidates and
unresolved inventory/deployment gates remain visible, not blanket-approved.
## Verification record
Targeted checks include actual child execution, catastrophic regex CPU,
aggregate memory exhaustion, TAR extension metadata, noisy output, malformed
transport/staging data, Unicode allocation limits, cancellation/callback
failures, descendant cleanup, rollback and explicit retries. Local mixed-owner
composition tests completed nine real children, rejected six overlapping
requests as busy, observed at most one unreaped child and recovered all slots.
This is local admission evidence, not a target deployment load certification.
Workflow fixtures serialize 40 different pinned revisions with five SQL reads;
summary lists use one query for 40 ordinary rows. Exact inbox totals for
40/400/4,000 candidates used one query, with measured local costs approximately
0.011/0.057/0.492 seconds. These are fixture measurements, not production SLOs.
The broader Core API smoke suite exposed three stale campaign assertions.
All three failures were reproduced against the unchanged private frozen 0.1.45
sources. Updated fixtures verify recipient-summary projection, detailed payload
separation and explicit fenced recovery of a confirmed stopped runtime; observing
SENDING alone must not make a claim recoverable. All 76 smoke tests then passed.
No production Campaign behavior was changed to satisfy these tests.
The final release-tool suite passed 279 tests and 68 subtests, including
temporary local remotes and adversarial source/tag/receipt changes. Rechecking
the whole batch before effects is deliberately conservative: its repeated
filesystem/Git/remote work grows quadratically with batch size. It is not a
new unattended publication path or permission to execute unreviewed source.
Strict interface inventory now reports no unclassified endpoints and exact
contextual help for all 133 high-risk controls. Seventeen password browser cases
include actual F1 help from the restricted screen, empty workspace scopes,
EN/DE layouts, Unicode boundaries and no credential values in help URLs.
The initial production bundle remains within the unchanged limits (512,036
raw bytes and 162,415 gzip bytes; 1,713 gzip bytes below its ceiling), with
46 optional descriptors and no eager optional-module imports.
The focused checker now includes the new Core process, mixed-owner admission,
Access password, Templates and Files worker tests, the repaired campaign smoke
cases, and browser-side auth/password transport contracts. The full focused run
passed, including 63 production module/build permutations and all 230 browser
cases. Its two opt-in Datasources PostgreSQL cases were skipped in that run
and subsequently passed against the isolated real database described below.
The final Meta preparation gate was added after that full run and verified
with the owning release-tool suite and the focused release-gate command.
Manifest validation passed for all 72 modules. The full focused log is
`/mnt/DATA/tmp/govoplan-security-followup-20260908-focused.log`.
The first follow-up quick audit captured an unchanged 79-repository snapshot
in `/mnt/DATA/tmp/govoplan-security-followup-quick-20260908-7s8Sgh/`.
All four required scanners completed, with zero missing/execution reports;
all 168 report checksums and 163 machine-readable reports were validated.
Gitleaks found no secrets in all 79 histories and 79 worktrees. Local Semgrep
rules reported zero findings. Production Bandit reported 65 low and four medium
warnings, and production Ruff retained 54 warnings. The two added Bandit
warnings identify the new Core subprocess import and invocation: trusted
server-owned arguments, no shell, and the documented resource/process boundary
were reviewed; warnings remain visible. This is report-only evidence, not a
warning-free audit or a penetration test. A final snapshot follows the
cross-module declaration/contextual-help corrections and release-tool checks.
That final audit completed on 8 September, 05:59:4606:02:18 UTC, in
`/mnt/DATA/tmp/govoplan-security-final-quick-20260908-vAwQIh/`. All 79 start/end
source fingerprints were identical; all four scanners completed, all 168
registered report checksums matched, and all 163 JSON/SARIF reports parsed.
There were no missing reports or scanner execution errors. Semgrep and both
Gitleaks scopes again reported zero findings. Production counts were unchanged
from the first follow-up: Bandit 65 low/four medium and Ruff 54. Test-only
counts were Bandit 140 low/34 medium and Ruff 136. A separate frozen scan of
all ten changed Meta release/deployment Python files reported seven low Bandit
and four Ruff S603 warnings, with no execution errors. Its four argv-only
subprocess sites were reviewed; the preparation additions introduced no new
warnings. No findings were hidden or severity-wide exceptions added.
The audit manifest SHA-256 is
`a997b786239cd11443cb665d5f9041a968cc38f9d49171e68bb868bf2bd73310`;
its report-checksum list SHA-256 is
`dc590ca5b0a4e445019a05536d410226088d67b40d61cd7657bdef4a4eae56d8`.
The audit includes the eight committed feature/website source changes and the
final uncommitted Meta source. Only this evidence document was updated after
the source freeze ended; the final Meta commit and remote publication are
recorded in the linked Gitea issues, not inferred from local audit completion.
Fresh dependency audits are retained in
`/mnt/DATA/tmp/govoplan-dependency-final-20260908-d24LEK/`: all four full npm
lockfile audits (Core WebUI, Mail root/WebUI and website) report zero known
vulnerabilities. Installed Python auditing covers 137 distributions with zero
known vulnerabilities; 51 local GovOPlaN distributions lack PyPI advisory
coverage. Core's 46 linked packages are likewise not claimed covered by public
registry advisories. All 12 dependency-file hashes and the installed inventory
were unchanged. No packages were installed or automatically fixed.
Managed PostgreSQL 16.15 fixtures used private Unix sockets, synthetic roles
and databases, no TCP listener, per-case schemas and bounded SQL/lock waits.
Both previously skipped Datasources races passed. Twenty-one existing Access
password HTTP tests and four additional races passed on PostgreSQL: single-use
redemption, stale-session/password replacement, competing issuance, and issuer
password revocation during redemption. Four release/development migration checks
also passed for Access and Workflow, including credential preservation and
idempotent indexes. The four races are now owning opt-in Access regressions;
see `govoplan-access/docs/PASSWORD_RECOVERY_POSTGRES_TESTS.md`. These local
database checks do not certify a deployment, fleet load or external recovery
handover. With both explicit PostgreSQL test URLs enabled, the full Access suite
passed 122 tests and 18 subtests, and the full Datasources suite passed 57 tests,
without skips. Access retained 12 existing SQLite datetime-adapter warnings in
its separate SQLite migration cases. Both temporary PostgreSQL fixtures were
stopped and independently verified: no server process, private socket,
generated schema or synthetic cluster remains. Scripts, logs and shutdown
receipts are retained under `/home/zemion/.cache/govoplan-pg-security-20260908.RAUitg/`
and `/home/zemion/.cache/govoplan-pg-promoted-20260908.JZcIKL/`.
## Adoption and remaining gates
1. `AUTH_LOCAL_PASSWORD_RECOVERY_ENABLED` remains **false** by default. The
existing flag is still advisory until an operator explicitly adopts and
enables the complete recovery policy. Confirm who verifies identity and how
the one-time code is handed over; automated email recovery is not enabled.
Test first-login, lost-password, code expiry and administrator availability
in the target environment before enforcement.
2. Access migration `e9a2c5f8b1d4` adds recovery evidence; Workflow migration
`9e6b3f8a2c7d` adds summary-pagination indexes. Use normal backed-up upgrade
procedures and account for index-build cost. No manual live migration or
server restart was performed during this work. The user's existing devserver
has automatic reload, so live schema state must not be assumed unchanged.
3. Release preparation must assign new source/package versions and require a
Core version containing the new worker/auth contracts in the affected module
metadata, including matching WebUI assets. The old immutable release must
not be relabelled or treated as containing these APIs.
4. Resource limits are not an arbitrary-code, filesystem or network sandbox.
Admission is per API/worker process, not fleet-wide. Validate Linux/cgroup
memory, disk quotas, process counts, cancellation and legitimate large-file
workloads on the intended runtime before increasing concurrency. Core #297
retains this target-evidence follow-up.
5. Meta #52 and website #9 retain runtime-image/publication/deployment holds.
Docker/Podman/HAProxy executables are unavailable here. Final built images,
binary/source inventories, ingress behavior, migration/readiness/worker
smoke checks and the website's target/operator authority remain outstanding.
Zero findings in a detected package inventory is not full image coverage.
No real messages, IMAP appends, password resets, provider operations or deployment
actions were used as test fixtures. Development tests use temporary databases,
private temporary files, mock transports and managed test-browser servers.
@@ -199,3 +199,9 @@ they do not change its immutable tags or published package bytes.
These are unreleased follow-up source changes, not a new runtime release or
deployment. The runtime-image hold under Meta #52 remains in force; the
historical peer-dependency workaround still needs its separate review.
Further implementation and adoption gates are tracked in the
[security follow-up](SECURITY_FOLLOWUP_2026-09-08.md), including disposable
parsing/execution workers, opt-in password recovery, workflow read projections
and the newer runtime-image evidence. The original scanner counts above remain
historical and are not silently replaced by later test results.
@@ -0,0 +1,305 @@
{
"schema_version": 1,
"purpose": "Audit evidence only; not a release manifest or active installer configuration.",
"observed_at": "2026-09-08T03:56:47.666808+00:00",
"runtime_publication_held": true,
"website_deployment_held": true,
"scan_execution_complete": true,
"coverage_complete": false,
"scanner": {
"name": "Trivy",
"version": "0.74.0",
"binary_sha256": "d89bcc6510a267f11b773398cbf1be5520ce39f9e8b6633178c4487f05b7d791",
"database_metadata": {
"Version": 2,
"NextUpdate": "2026-09-08T19:06:01.154199291Z",
"UpdatedAt": "2026-09-07T19:06:01.154199452Z",
"DownloadedAt": "2026-09-07T23:30:53.198039224Z"
},
"source": "remote",
"scanners": [
"vuln"
],
"list_all_packages": true,
"images_executed": false,
"existing_docker_credentials_used": false,
"timeout": "8m",
"maximum_image_size": "2GB"
},
"evidence": {
"private_directory": "/home/zemion/.cache/govoplan-runtime-remediation.qfDSWHJh",
"summary_sha256": "0d44390408ab35270e4430516f77bf11aa7877334eff2ef19e11e9a863fe5c56",
"frozen_images_sha256": "d0cb156f4a88998531ec55ab950067a3f1350ded648f07650c463af101dad467",
"scanner_script_sha256": "f64c69e06efc2ad7b5a657a25aa73f9ff586e3685737d525456bcecbe3ab5f07"
},
"candidates": [
{
"name": "haproxy",
"image": "docker.io/library/haproxy:3.2.23-alpine@sha256:6343ce34a132a5dceaa24767d739df2bd519f8f7c1079ae39e4821334e8eb42e",
"disposition": "source_default_updated_binary_runtime_verification_pending",
"platforms": [
{
"platform": "linux/amd64",
"manifest_digest": "sha256:0666a2c2f41d341084ed2da85392b48cdcd766adfa28231f31305724ed5c6ea5",
"config_digest": "sha256:9621d75e50a8f26d3738ae3cdbf15e98c6aa5cd48e6baca0699492de502156f5",
"compressed_layer_bytes": 20516844,
"scan_exit_code": 0,
"os": {
"Family": "alpine",
"Name": "3.24.1"
},
"inventory": [
{
"type": "alpine",
"packages": 24
}
],
"counts": {
"CRITICAL": 0,
"HIGH": 0,
"MEDIUM": 0,
"LOW": 0,
"UNKNOWN": 0
},
"fixable_high_critical": 0,
"unique_cves": 0,
"report_sha256": "40cfc3db74e29f09723f38698660ccdd50ac935c8d6e1e75c6e0555b3e9361fb",
"log_sha256": "46881f695780f89c037d5b0b4dc0ded9ea4e459077c7658d80b786efc5754084"
},
{
"platform": "linux/arm64",
"manifest_digest": "sha256:cd20b9dc6b4713956a2a043997001a1948167d345e7c8d5bd5ff2e667166651f",
"config_digest": "sha256:19796bff8905d4a46c9463c576203b20cac8984d41b7b01ea9cbff55e8422c34",
"compressed_layer_bytes": 20970772,
"scan_exit_code": 0,
"os": {
"Family": "alpine",
"Name": "3.24.1"
},
"inventory": [
{
"type": "alpine",
"packages": 24
}
],
"counts": {
"CRITICAL": 0,
"HIGH": 0,
"MEDIUM": 0,
"LOW": 0,
"UNKNOWN": 0
},
"fixable_high_critical": 0,
"unique_cves": 0,
"report_sha256": "0e1326c58abf358d592fa55c94333228ce1094edf4e489fcc4ece6e32d3320f6",
"log_sha256": "397c2fbf1044cf50733d68b4b9cc4eb68211d96f1f302d5e9f8af0d11fb0de1c"
}
]
},
{
"name": "nginx-stable",
"image": "docker.io/nginxinc/nginx-unprivileged:1.30.4-alpine@sha256:442753882674b49ae2c1de83ed67896131c0777f56df5005e356e62bc3f7e7ce",
"disposition": "candidate_pending_compatibility",
"platforms": [
{
"platform": "linux/amd64",
"manifest_digest": "sha256:b8c179cd3c2ae222a873dd59fbae240fadc03836cae5198afc9e9c19919c3880",
"config_digest": "sha256:8b5953dae38d27a76bca22373bb920fd6ce8d9d7da21578d2926e678002de8a0",
"compressed_layer_bytes": 25526590,
"scan_exit_code": 0,
"os": {
"Family": "alpine",
"Name": "3.24.1"
},
"inventory": [
{
"type": "alpine",
"packages": 70
}
],
"counts": {
"CRITICAL": 0,
"HIGH": 0,
"MEDIUM": 0,
"LOW": 0,
"UNKNOWN": 0
},
"fixable_high_critical": 0,
"unique_cves": 0,
"report_sha256": "3ad164dae3cdf891b12e41451b8a8e65a5e1688824a7c01d848e1274363e6bf9",
"log_sha256": "0f99ff3d9b4396de48655bf8299df30c14ba0c579f480d37baa7d2f6a4c11f1d"
},
{
"platform": "linux/arm64",
"manifest_digest": "sha256:b6742a0cbd749add25346658991c3da06a8e38796949df120fed78db8c512576",
"config_digest": "sha256:4d8b10f5d2ff99e7aa5f161930d8a4693a86a26f288ec8c0d4cd47f2ef5af179",
"compressed_layer_bytes": 25892370,
"scan_exit_code": 0,
"os": {
"Family": "alpine",
"Name": "3.24.1"
},
"inventory": [
{
"type": "alpine",
"packages": 70
}
],
"counts": {
"CRITICAL": 0,
"HIGH": 0,
"MEDIUM": 0,
"LOW": 0,
"UNKNOWN": 0
},
"fixable_high_critical": 0,
"unique_cves": 0,
"report_sha256": "506fdeb97525ed8e4d9ae538c42bab7aa2217f662a7135f0f12d16d20410c7a6",
"log_sha256": "c41ef9ea091d00f18c7a097404672591bee85e7477ae17d8f5ca771d8e42b2bb"
}
]
},
{
"name": "caddy",
"image": "docker.io/library/caddy:2.11.4-alpine@sha256:5f5c8640aae01df9654968d946d8f1a56c497f1dd5c5cda4cf95ab7c14d58648",
"disposition": "not_selected_remaining_fixable_findings",
"platforms": [
{
"platform": "linux/amd64",
"manifest_digest": "sha256:98eb57d882ccd5213d1688764db10c1ca2c58a1ca3a6717a3411ad798f7a423a",
"config_digest": "sha256:af555904a0961945f16bb323a501457b13a4f7e9bde969b145b97da80b38ecbe",
"compressed_layer_bytes": 23907283,
"scan_exit_code": 0,
"os": {
"Family": "alpine",
"Name": "3.23.5"
},
"inventory": [
{
"type": "alpine",
"packages": 32
},
{
"type": "gobinary",
"packages": 146
}
],
"counts": {
"CRITICAL": 1,
"HIGH": 38,
"MEDIUM": 41,
"LOW": 12,
"UNKNOWN": 23
},
"fixable_high_critical": 39,
"unique_cves": 68,
"report_sha256": "e483352a1d5b9b97950dcf92e4dfcf4600f5b09306af3d0640b10ca229a07779",
"log_sha256": "9cd599d6dd8c101b421fdeb57036cec1f69f815d765a8bf1f850ec60061036f4"
},
{
"platform": "linux/arm64",
"manifest_digest": "sha256:1172d4213087d3fc30bafc7ff2c2896180eb0c41ff7f75f315568fb36cabdcba",
"config_digest": "sha256:6b08c1b9858ca9a7d99c1da13c3695081e0e604c6cf214ca26a7ce0e2c4fd9b4",
"compressed_layer_bytes": 22722712,
"scan_exit_code": 0,
"os": {
"Family": "alpine",
"Name": "3.23.5"
},
"inventory": [
{
"type": "alpine",
"packages": 32
},
{
"type": "gobinary",
"packages": 146
}
],
"counts": {
"CRITICAL": 1,
"HIGH": 38,
"MEDIUM": 41,
"LOW": 12,
"UNKNOWN": 23
},
"fixable_high_critical": 39,
"unique_cves": 68,
"report_sha256": "20e04d820e3b27d57575ea177e523e23109fd83344cdf57e184a4d2fad27e803",
"log_sha256": "a1d9c37aa7948c137db64040d95e5930c5859a8acd71ac5bc41ff168e2b270c5"
}
]
},
{
"name": "node-lts",
"image": "docker.io/library/node:24-alpine@sha256:e67514e5d0f6c46656005e1b693b2ec9d52e80b641307de684d4a015ba7a4eaf",
"disposition": "not_selected_remaining_fixable_findings",
"platforms": [
{
"platform": "linux/amd64",
"manifest_digest": "sha256:4caaaf42195bcd6f6f3559a413b20cb8f8ad089e231ee874cf7701643966689f",
"config_digest": "sha256:ee289c69ed1ac50a5a042112ea97f132800e2dd53e832da27784f00e45b3289c",
"compressed_layer_bytes": 58486244,
"scan_exit_code": 0,
"os": {
"Family": "alpine",
"Name": "3.24.1"
},
"inventory": [
{
"type": "alpine",
"packages": 18
},
{
"type": "node-pkg",
"packages": 146
}
],
"counts": {
"CRITICAL": 0,
"HIGH": 6,
"MEDIUM": 11,
"LOW": 12,
"UNKNOWN": 0
},
"fixable_high_critical": 6,
"unique_cves": 19,
"report_sha256": "c927d995dde700c92027f6328dc6c273f0f1445cd8154301480757cb962e02b2",
"log_sha256": "c7dc1900cbe39c9f91e228b2770bcbd394ec2914d2b3879478295fc7f8f77ab3"
},
{
"platform": "linux/arm64",
"manifest_digest": "sha256:d3724e44ee368606d753e0027eb8d2a94fc1f275e5d9e4620178a12edb655f5f",
"config_digest": "sha256:722cc1507731edf58a4c0bc3e29553c44ce5774d0849242c1b237abc79926a0a",
"compressed_layer_bytes": 58935654,
"scan_exit_code": 0,
"os": {
"Family": "alpine",
"Name": "3.24.1"
},
"inventory": [
{
"type": "alpine",
"packages": 18
},
{
"type": "node-pkg",
"packages": 146
}
],
"counts": {
"CRITICAL": 0,
"HIGH": 6,
"MEDIUM": 11,
"LOW": 12,
"UNKNOWN": 0
},
"fixable_high_critical": 6,
"unique_cves": 19,
"report_sha256": "e5f7799761f23cefc36929381b4186eeb3afb46a2a559c789d12a7eea5dc30d0",
"log_sha256": "fd24671f0da4d3b20dc8bcc2718531e6f6e19155d49bed15958965e21dd10813"
}
]
}
]
}