diff --git a/docs/operations/PACKAGE_REGISTRY_RELEASES.md b/docs/operations/PACKAGE_REGISTRY_RELEASES.md index decd271..ad1e809 100644 --- a/docs/operations/PACKAGE_REGISTRY_RELEASES.md +++ b/docs/operations/PACKAGE_REGISTRY_RELEASES.md @@ -86,6 +86,16 @@ contract. The coordinated release synchronizes `peerDependencies` and tag, then synchronizes each lockfile root from the final package metadata. A distinct root package remains independent. +Every module referenced by Core's Git-based `package.release.json` must expose +its WebUI identity at the repository root, including matching peer requirements +and `webui/`-prefixed entry exports (also CSS subpaths). npm resolves Git +dependencies from the repository root, while the native-package workflow packs +`webui/`; success in one path does not verify the other. Run +`python tools/checks/check-webui-package-facades.py` after changing either +manifest or the release composition. The focused gate also runs this check. +Adding or correcting a facade in an already published repository requires a +new patch tag; never repair an existing immutable tag in place. + It builds one wheel and, where applicable, one npm tarball. The workflow records the source tag, source commit, filename, size, and SHA-256 in `package-artifacts.json` before publishing. Gitea rejects a second upload of the diff --git a/docs/releases/0.1.45.md b/docs/releases/0.1.45.md new file mode 100644 index 0000000..c2da74e --- /dev/null +++ b/docs/releases/0.1.45.md @@ -0,0 +1,80 @@ +# GovOPlaN 0.1.45 — usability, reliability and security hardening + +Release coordination: [GovOPlaN #51](https://git.add-ideas.de/GovOPlaN/govoplan/issues/51). +The exact independently versioned composition is recorded in +`packages/govoplan-meta/pyproject.toml`; unchanged modules retain their versions. +This source release does not by itself establish a deployed or independently +approved production environment. Package, signed catalog and runtime publication +results are recorded separately in the coordination issue. + +## Runtime publication hold + +The [runtime image audit](../security/RUNTIME_IMAGE_AUDIT_2026-09-08.md) completed +eleven registry-only amd64 scans, but found unresolved vulnerabilities and +inventory gaps. Runtime publication remains held separately from this source +release. Patch-only image updates are insufficient; maintained minor-line +changes, narrowly evidenced finding decisions, arm64/final-layer scans and +deployment checks remain necessary. No audited candidate was automatically +adopted and no image was executed during those scans. +The remaining gates are tracked in +[GovOPlaN #52](https://git.add-ideas.de/GovOPlaN/govoplan/issues/52). + +## Included changes + +- Shared page/action placement, reusable navigation grouping/editing, table and + dialog sizing, field alignment, multi-select filters and predictable tree + selection. Files, Mail, Search, Notifications and domain pages use the same + contracts, with browser regression coverage. +- Campaign draft saving and independent Mail/ZIP-policy repair, persistent and + bulk message review, clearer delivery eligibility, bounded configurable + synchronous delivery, guarded workerless recovery, lightweight SMTP/IMAP + progress, reused IMAP connections and recipient-complete reporting. +- Files archive staging/reuse, unpacking previously uploaded archives, numeric + progress and bounded traversal. Optional native archive acceleration retains + the same validation rules; portable fallbacks remain available. +- Mail credential references and IMAP folder-name decoding; help topics can be + found by area and tags without expanding every occurrence of the same topic. +- Authentication provenance/scope and browser-cache hardening, patched rich-text + dependencies, spreadsheet/archive/template/Dataflow resource limits, batched + Docs/Notifications queries and safe Reporting bind names. See the + [security/performance review](../security/SECURITY_PERFORMANCE_REVIEW_2026-09-08.md) + for measurements, test evidence and remaining limitations. +- A deterministic governance-journey clock fixture, fresh-process Campaign + import coverage, and a new Cases patch aligning its root npm facade with its + Python/WebUI package. Historical published tags are not rewritten. +- Git-root WebUI package facades are aligned with their owning packages, with + a cross-composition parity check. Tasks is included in default module + discovery; it remains subject to enabled modules and normal permissions. + +## Upgrade and verification + +Back up the database and file storage before upgrading. Apply the complete +selected migration graph before starting the new API/workers. This release +includes additive repair migrations `c58a2d7e9f10` (Core ownership history) and +`d8f1b4e7a0c3` (Access external-function mappings), plus Campaign delivery-state +migrations. Existing business evidence is retained; a schema downgrade is not +a substitute for a reviewed backup/restore plan. Restart API and worker +processes together after upgrading their matching packages. + +Updated UI consumers require Core 0.1.45 where they use its new shared contracts. +Tenant keys that previously relied on unintended system permissions/wildcards +must be corrected; the release does not preserve that unsafe behavior. Extremely +sparse spreadsheets, oversized generated output and excessive archive paths +can now fail early with a diagnostic. + +For archive staging across multiple hosts, provide shared POSIX storage with +working locks or sticky routing. Background delivery still needs configured +workers; increasing the synchronous limit does not create a worker or guarantee +delivery after a process failure. An unknown SMTP outcome must be reconciled, +not automatically resent. + +After deployment, manually verify login/logout and least-privilege API keys, +Campaign Settings and independent Mail/ZIP saves, archive upload/unpack, +recipient-complete reports, and SMTP/IMAP progress with an explicitly approved +test mailbox. No release verification sends real campaign mail automatically. + +Hard process isolation, forced-password-change/recovery enforcement, bounded +Xrechnung subprocess output and large-history pagination remain separate open +issues. This release is not a claim that all security or performance debt is +resolved. Production-image scans and multi-host evidence must refer to the +actual signed runtime being deployed. diff --git a/docs/security/RUNTIME_IMAGE_AUDIT_2026-09-08.md b/docs/security/RUNTIME_IMAGE_AUDIT_2026-09-08.md new file mode 100644 index 0000000..50d76e0 --- /dev/null +++ b/docs/security/RUNTIME_IMAGE_AUDIT_2026-09-08.md @@ -0,0 +1,125 @@ +# Runtime image candidate audit — 8 September 2026 + +Release coordination: [GovOPlaN #51](https://git.add-ideas.de/GovOPlaN/govoplan/issues/51). +Canonical remediation: [GovOPlaN #52](https://git.add-ideas.de/GovOPlaN/govoplan/issues/52). +This follow-up to the [source security/performance review](SECURITY_PERFORMANCE_REVIEW_2026-09-08.md) +records registry-only scans of nine proposed runtime dependencies and two +same-minor patch candidates. **Runtime publication is held:** patch-only updates +do not resolve the baseline. Source/package publication is a separate outcome. +No images were executed, rebuilt, selected for CI, or published by this audit. + +## Method and reproducible evidence + +Official Trivy **0.74.0** was installed only in a private local task directory, +without sudo or Docker access. Its Linux-64bit release archive matched both the +official checksums file and GitHub release asset metadata: + +- Archive SHA256: `2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a`. +- Checksums-file SHA256: `bc701c3c3ee8b9acbea2c23257e41381e3854888f51281616a6ba5dc96963821`. +- Vulnerability database schema 2, updated `2026-09-07T19:06:01.154199452Z`, + downloaded from `mirror.gcr.io/aquasec/trivy-db:2`. +- Scan flags: `--image-src remote --platform linux/amd64 --scanners vuln + --format json --no-progress --timeout 8m --max-image-size 2GB --exit-code 0`. + Findings were counted from validated JSON; exit zero did not mean clean. +- Existing Docker credentials were not read; no private keys or secrets were + used. Checksums over official HTTPS metadata were verified, not independent + Sigstore signatures. See the [official release](https://github.com/aquasecurity/trivy/releases/tag/v0.74.0) + and [registry-only scan documentation](https://trivy.dev/docs/latest/target/container_image/). + +Raw evidence is retained locally, not committed: +`/home/zemion/.cache/govoplan-trivy-remote.yKZgjDOg/scan/`. +It contains eleven `reports/*-amd64.json` reports/logs, scanner scripts, +`patch-candidate-inspection.json`, exact successor registry indices, and +`evidence-checksums.json`. Summary SHA256 values: + +- `summary.json`: `f2785a731d637452ab9c0b1f5399772c0f8828a63ca83d5fa7496abdad1c757a`. +- `patch-summary.json`: `b3fc6273fcdad98864040ccdf3477ecf379afd46e9f94444b1f2910f48c1d85b`. + +All eleven executions succeeded without timeout/rate-limit failure. Initial +summary fields distinguish `scan_execution_complete: true` from +`coverage_complete: false`: Garage has no detectable package inventory. +Checksums preserve evidence identity, not indefinite storage availability. + +## Exact requested pins and results + +All references below use `docker.io/`. Counts are package-vulnerability records, +not distinct CVEs or confirmed exploitable application defects. A vulnerability +can appear against several installed packages. Unfixed/unknown records remain. + +| Image tag | Exact index SHA256 | Critical / High / Medium / Low / Unknown | Fixable C/H | +| --- | --- | --- | ---: | +| `library/python:3.12-slim-bookworm` | `782412e85d0f0984994c290652577d4018aff08145c85b262bb63dc0c7522254` | 5 / 55 / 102 / 103 / 5 | 0 | +| `library/postgres:16-alpine` | `cf78e76683b9ca8c5733cbbdce6c9262b45b6767934dd0a95e671f9a0fc20685` | 1 / 30 / 28 / 14 / 1 | 31 | +| `library/redis:7-alpine` | `ff02b58f971e7d7d156a1267e283fcbbeee91773b6aa36c49dac28ecfe28eadf` | 0 / 0 / 0 / 0 / 0 | 0 | +| `nginxinc/nginx-unprivileged:1.29-alpine` | `0c79d56aee561a1d81c63f00eee5fb5fe29279560cdc55e91425133104c7fbe6` | 0 / 33 / 74 / 37 / 20 | 33 | +| `library/haproxy:3.2.21-alpine` | `66e25cc9a8332635f4e897f7f4b1e5622c25f09f0ee23cddc6ce9bdb3a24772a` | 0 / 2 / 6 / 12 / 0 | 2 | +| `library/caddy:2.10.2-alpine` | `4c6e91c6ed0e2fa03efd5b44747b625fec79bc9cd06ac5235a779726618e530d` | 7 / 75 / 67 / 37 / 4 | 82 | +| `dxflrs/garage:v2.3.0` | `866bd13ed2038ba7e7190e840482bc27234c4afaf77be8cfa439ae088c1e4690` | **Unknown: no inventory** | — | +| `greenmail/standalone:2.1.9` | `3ac5a83dd6727cf95e4d50e18907fb8ee7bbf5f67e8534714dee2fb1b5b2e1d4` | 0 / 0 / 116 / 35 / 0 | 0 | +| `tonistiigi/binfmt:qemu-v10.2.3-68` | `400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0` | 0 / 9 / 2 / 1 / 1 | 9 | + +## Patch-only options and limits + +Complete publisher tag listings were inspected for nginx 1.29, Caddy 2.10, +HAProxy 3.2, GreenMail 2.1 and binfmt qemu10.2. Two newer candidates were +scanned; their registry index bytes matched both registry and publisher digests, +and contained amd64 and arm64 manifests: + +- `library/haproxy:3.2.23-alpine@sha256:6343ce34a132a5dceaa24767d739df2bd519f8f7c1079ae39e4821334e8eb42e`: + same Alpine 3.24.1, 24 detected OS packages, zero reported findings. This is + a useful candidate, not a completed compatibility test or application audit. +- `greenmail/standalone:2.1.13@sha256:3df66b7edd01c8a301343ca5e3601d8674760d4708655573560c24745e624fb2`: + upstream changes Ubuntu 22.04 to Debian 13.6; **3 C / 80 H / 98 M / 85 L / + 5 unknown**, 30 fixable C/H records. Not selected as a no-base-change update. +- nginx's newest matching Alpine patch is already 1.29.8 at the scanned pin; + Caddy 2.10 remains 2.10.2; binfmt qemu10.2 remains 10.2.3-68. No newer matching + publisher images were found. The current [official Caddy image catalogue](https://raw.githubusercontent.com/docker-library/official-images/master/library/caddy) + uses 2.11.4; switching minor lines requires new scans and compatibility checks. + +Priority remediation: Caddy's own seven HIGH records require fixes through +2.11.4, with additional bundled Go/library fixes that must be re-scanned; +nginx's packages include curl/libcurl fixes through 8.22.0-r0, OpenSSL 3.5.8-r0, +c-ares 1.34.8-r0, expat 2.8.1-r0 and libuuid 2.41.6-r1. PostgreSQL's OS records +require OpenSSL 3.5.8-r0 and libuuid 2.42.3-r1; its CRITICAL plus 21 HIGH Go +records concern the **gosu helper**, not PostgreSQL server code. binfmt's nine +HIGH records concern its Go 1.26.4 build, with fixes through 1.26.6. Package +presence does not establish vulnerable-symbol reachability. No unscanned tag +is claimed to meet every fix requirement. + +## Python triage and coverage caveats + +Python image metadata identifies CPython 3.12.14, but Trivy inventories only +Debian packages and pip, **not CPython/stdlib**. All 60 C/H records concern +Debian packages: 21 CVEs, 50 `affected` records, 9 `fix_deferred`, 1 +`will_not_fix`, without a recorded fixed Bookworm version. Five util-linux CVEs +repeat across eight binary packages. These remain installed; they are not all +removed build dependencies. Pip 25.0.1 separately has five MEDIUM/one LOW +records, with fixes through 26.2.0; it is install tooling, and the API image uses +an offline `--no-index` wheelhouse rather than an arbitrary package index. + +Narrow triage examples, **not blanket exemptions**: + +- Debian states [CVE-2023-45853](https://security-tracker.debian.org/tracker/CVE-2023-45853) + does not affect the built Bookworm zlib binaries because vulnerable minizip + code is not included. Other bundled minizip implementations are separate. +- [CVE-2026-8376](https://security-tracker.debian.org/tracker/CVE-2026-8376) + explicitly requires 32-bit Perl; this scan targets amd64. +- [CVE-2025-7458](https://security-tracker.debian.org/tracker/CVE-2025-7458) + requires crafted arbitrary SQLite SQL; the managed runtime uses PostgreSQL, + but alternate SQLite use must be reviewed. +- Perl's regex and Archive::Tar records need exact binary/module applicability + checks; vendor-deferred status alone is not a finding dismissal. + +Only amd64 was scanned. arm64, newly built GovOPlaN API/Web layers and optional +dependency combinations remain unverified. Garage has no inventory; Redis, +HAProxy and PostgreSQL source-built executables, CPython and QEMU static +binaries need supplemental SBOM/source coverage. Zero detected OS findings is +not zero application vulnerabilities. Trivy also lacks Alpine 3.24 EOL metadata +and nginx CVE-2026-80256 detail; unknowns are retained. There were no runtime, +exploitability, secret, misconfiguration, malware or signature-policy checks. + +Before lifting the runtime hold: approve and test maintained image-line changes +where necessary, fix or narrowly disposition findings with evidence, close +inventory gaps, scan both architectures and final runtime layers, then run +deployment/ingress smoke checks. Do not silently change base OS, use unpinned +`latest`, rebuild third-party images, or accept all HIGH/CRITICAL findings. diff --git a/docs/security/SECURITY_PERFORMANCE_REVIEW_2026-09-08.md b/docs/security/SECURITY_PERFORMANCE_REVIEW_2026-09-08.md new file mode 100644 index 0000000..83cfa39 --- /dev/null +++ b/docs/security/SECURITY_PERFORMANCE_REVIEW_2026-09-08.md @@ -0,0 +1,176 @@ +# Security and performance review — 8 September 2026 + +Coordinated status: [Core #296](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/296). +This records a workspace-wide automated scan, targeted manual boundary review, +and a verified implementation pass. It is not a penetration test, an exhaustive +line-by-line review, or a security certification. The audit was completed on +local, unpublished changes, preserving existing worktree changes. Subsequent +release preparation/publication is tracked in +[GovOPlaN #51](https://git.add-ideas.de/GovOPlaN/govoplan/issues/51) and the +[0.1.45 release notes](../releases/0.1.45.md). + +## Implemented findings + +| Area | Finding and change | Evidence / ownership | +| --- | --- | --- | +| Authentication — high | Preserve service-account provenance and current scope ceilings instead of recalculating them as ordinary membership permissions. Tenant API keys cannot retain canonical system permissions or unsafe wildcard grants. | Previously failing isolated regressions; [Access #21](https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/21). | +| Authentication — medium | Warm-cache API keys must follow the same explicit-header credential rules as cold authentication. A session cookie cannot turn an API key into a session credential. | Regression covering source-dependent authentication. | +| Browser authority/cache — medium | Clear reusable data on auth changes and write settlement; fence late 200/304 writes and obsolete 401 side effects. Honor server no-store/no-cache and explicit fresh-read requests. Interactive login/logout remove retained automation keys that could shadow cookie-session identity. | 23 real-client regressions. Unchanged settings keep their object identity, preventing profile-fetch loops. Core `docs/API_CLIENT_CACHE_CONTRACT.md`; owning Access EN/DE session/field documentation. | +| Spreadsheet resource exhaustion | Validate actual XLSX coordinates before openpyxl traversal; ignore misleading declared dimensions; count blank row gaps toward the existing limits. | [Connectors #18](https://git.add-ideas.de/GovOPlaN/govoplan-connectors/issues/18), 13 tests and 2 subtests. | +| Template resource exhaustion | Enforce the existing 5 MiB output budget during substitution and item construction, including UTF-8, HTML escaping and separators. | [Templates #7](https://git.add-ideas.de/GovOPlaN/govoplan-templates/issues/7), full 20 tests; independent 3,000-case valid-output comparison. | +| Archive resource exhaustion | Inspect regular TAR member limits before traversing payloads. Limit archive paths to 4,096 UTF-8 bytes / 128 components and count derived directories against entry limits. | [Files #46](https://git.add-ideas.de/GovOPlaN/govoplan-files/issues/46), 55 archive and 15 documentation tests. Extension-header decoding still needs stronger isolation. | +| Dataflow resource exhaustion | Reject LPAD/RPAD target lengths above the existing 1,000,000-byte preview budget before fill evaluation/allocation. Preserve final serialized-byte checks. | [Dataflow #22](https://git.add-ideas.de/GovOPlaN/govoplan-dataflow/issues/22), full 104 tests and 39 subtests; 7 new guard tests independently rerun. | +| Docs performance / defense in depth | Batch revision reads per request, avoid loading pending draft bodies for readers, and validate tenant/entry/publication consistency while retaining owner/audience checks. | [Docs #22](https://git.add-ideas.de/GovOPlaN/govoplan-docs/issues/22), full 39 tests. | +| Notifications performance / defense in depth | Batch delivery-attempt loading while preserving recipient checks and rejecting inconsistent attempt references, including already-loaded relationships. | [Notifications #6](https://git.add-ideas.de/GovOPlaN/govoplan-notifications/issues/6), full 23 tests. | +| Session-list performance | Apply active/expiry predicates and the existing 100-row cap in SQL, before loading session history. | Query-shape regression in Access. | +| Reporting correctness | Use structural bind-name suffixes for recursive calculated measures, preserving valid dotted/hyphenated public keys and parameter uniqueness. | [Reporting #10](https://git.add-ideas.de/GovOPlaN/govoplan-reporting/issues/10), full 29 tests. | +| Audit hygiene | Redact Gitleaks logs and machine reports on current, history and legacy scanner paths. | 13 audit-wrapper tests enforce the flag. | + +All changed module workflows/limits have owning EN/DE DocumentationTopic updates. +Independent review found no concrete regression in the backend changes. + +## Measured performance changes + +These are SQL-query counts in isolated 40-item fixtures, not production latency +or throughput claims. Authorization is still evaluated for each request. + +| Projection | Before | After | +| --- | ---: | ---: | +| Docs reader entries | 41 SELECTs | 2 SELECTs | +| Docs editor entries | 81 SELECTs | 2 SELECTs | +| Notification list with attempts | 41 SELECTs | 2 SELECTs | + +The Docs 401-entry batching regression uses 3 SELECTs. Resource guards reject +oversized work before the formerly expensive allocation/traversal. This does +not make every legitimate upload or campaign faster. Honoring no-cache can +increase server validation requests; ETags still avoid retransmitting unchanged +bodies. That authorization/freshness trade-off is deliberate. + +The original audit snapshot measured 517,380 initial JavaScript bytes and +164,119 gzip bytes. Release preparation's pure-defaults split reduces this to +516,730 initial bytes and 163,908 gzip bytes. Restoring the missing Tasks +descriptor then measures 516,987 initial / 163,976 gzip bytes with all 46 module +descriptors lazy, within the unchanged 524,288 / 164,128 caps. The gzip margin is still small; future +startup work should reduce eager dependencies, not raise the cap automatically. +The full 209-case browser suite passed before the split, followed by 13 focused +browser checks after it. Radon recorded 238 rank-D-or-higher entries; complexity +is a review-priority signal, not a performance measurement. + +## Dependency remediation + +Core's full npm audit went from 30 affected package entries to zero. Most initial +entries were transitive effects of the same Tiptap advisory, not 30 independent +application exploits. The website went from two affected entries to zero; both +Mail lockfiles also report zero. + +- Tiptap packages are aligned at 3.31.3, with direct minimum ranges raised to + 3.30.4 in both development and release manifests, with a parity regression. + Added an actual installed-library prototype-attribute regression for + the [maintainer's security advisory](https://github.com/ueberdosis/tiptap/security/advisories/GHSA-cp6q-959q-f8rh). +- Core now resolves xmldom 0.9.12, browserslist 4.28.9 and nanoid 3.3.18. + The website's affected browserslist/nanoid dependencies are patched too. +- Development/audit requirements now require pip >=26.2; the local development + environment uses 26.2.1. The installed audit originally flagged + [CVE-2026-13346](https://github.com/advisories/GHSA-qwm4-qh6w-59xr), requiring an + attacker-controlled package index. This is an installation-tool vulnerability, + not evidence of an exposed application endpoint. + +The final installed Python audit enumerated 188 distributions: 137 were +auditable with zero known vulnerabilities, and 51 local distributions were not +available in PyPI. Those skips are covered by source review, not by a claim of +dependency-advisory coverage. Production images and every optional dependency +combination were not independently resolved or scanned. + +## Scan coverage and limitations + +Evidence directory: +`/mnt/DATA/tmp/govoplan-security-performance-20260908-gsk8jn/`. + +The final `final-quick/manifest.json` captures 79 repositories, tool versions, +start/end repository fingerprints, report checksums, 168 report artifacts and +163 validated JSON/SARIF reports. It records an unchanged workspace, complete +coverage for its four required scanners, no execution errors and no missing +reports. It ran in report-only mode: exit zero does **not** mean zero warnings. + +- Final production Bandit: 447,008 Python lines; 67 warnings (63 low, 4 medium), + no high findings. Ruff security rules: 54 warnings. SQL-construction warnings + were reviewed against identifier/operator validation and bound values in + DuckDB/Reporting; no injection fix was warranted there. XML import warnings + were checked: feed/BPMN input parsing uses defusedxml; stdlib imports support + types/output construction. Operator-owned fenced-run argv is not a public + arbitrary-command endpoint. Xrechnung output buffering remains a follow-up. + Assertions and error-swallowing markers remain review/maintenance warnings, + not proof that all such code is harmless. +- Final local Semgrep rules: no findings. The broader OWASP-rule pass applied + 272 rules to 4,169 tracked targets. Its seven warnings recommended weakening + owner-only 0700 permissions; they were rejected as false positives. One + Calendar rule timeout was rerun with a 60-second budget: zero findings/errors. + Bash and conformance TypeScript checks passed despite two scanner-specific + parser limitations. Ignored/dependency/generated paths are not a complete + line-by-line source audit. +- Gitleaks: 79 Git histories plus 79 worktrees, 158 redacted reports, zero + detected secrets. This does not establish that deployed credentials are safe + or that formerly exposed credentials have been rotated. +- Tool versions included Semgrep 1.176.1, Bandit 1.9.4, Ruff 0.15.21 and + Gitleaks 8.30.1. The downloaded Gitleaks binary archive matched the official + release SHA-256 before execution. +- The containerized full-toolbox path could not access Docker's daemon. Its + full-mode Trivy/misconfiguration and additional OSV scans were **not** run. + A subsequent [registry-only runtime image audit](RUNTIME_IMAGE_AUDIT_2026-09-08.md) + successfully scanned nine pinned candidates and two same-minor successors + for amd64 without Docker. It found unresolved vulnerabilities and inventory + gaps; runtime publication is held. This does not complete full-toolbox, + arm64, final-runtime-image or deployment coverage. + +No live application probes, database changes, file operations, mail sends, +IMAP appends, imports, notification delivery, deployments, commits or pushes +were performed. Browser tests used isolated mocked fixtures. Package installs, +builds and temporary audit-tool installation were local development operations. + +## Verification and remaining work + +- 209/209 browser conformance tests pass; production Core/website builds, + conformance TypeScript, 24 Core client/dependency regressions, 4 real-client + Files reload checks, and 72/72 manifest checks pass. +- Access's full 91-test suite passed before the final documentation-only update; + the final documentation suite passed all 4 tests. Other module counts appear + above. The new authentication/resource tests include demonstrated pre-fix + failures rather than only structural assertions. +- The original focused workspace run stopped at the institutional + governance/Portal fixture (`tests/test_institutional_governance_journey.py:223`, + `IndexError`). Release preparation fixes its mixed clocks using the existing + temporal context, retaining validity-boundary exclusions; 7 journey tests and + ambient-year checks pass. Tracked in + [Meta #50](https://git.add-ideas.de/GovOPlaN/govoplan/issues/50). +- Campaign's apparent host-path issue was ruled out by existing tracked + API/build/snapshot guards and 11 passing tests under normal initialization. + Release preparation fixes the standalone import cycle through a deferred + resolver import without changing validation rules. Fresh-process coverage, + all 11 path tests and Campaign's full 611-test suite pass. + +Next coordinated work: + +1. [Hard resource isolation — Core #297](https://git.add-ideas.de/GovOPlaN/govoplan-core/issues/297): + regex CPU, aggregate allocation, TAR extension metadata, bounded workers and + cancellation, followed by production-like concurrent load tests. +2. [Forced password change/recovery — Access #22](https://git.add-ideas.de/GovOPlaN/govoplan-access/issues/22): + the current flag is advisory only. Do not enable enforcement without a usable + local-password/recovery flow and external-provider rules. +3. [Bound subprocess output — Xrechnung #2](https://git.add-ideas.de/GovOPlaN/govoplan-xrechnung/issues/2): + enforce the existing 2 MiB limit while draining stdout/stderr, not afterwards. +4. [Workflow revision batching/history projection — Workflow Engine #3](https://git.add-ideas.de/GovOPlaN/govoplan-workflow-engine/issues/3): + batch evidence lookups; separately define explicit history pagination and + authorized-total semantics. Docs/notification history volumes also remain. +5. Resolve the [runtime image audit](RUNTIME_IMAGE_AUDIT_2026-09-08.md) findings + tracked in [Meta #52](https://git.add-ideas.de/GovOPlaN/govoplan/issues/52) + and coverage gaps before lifting its publication hold; complete deployment + audits, review exposed development credentials and worker quotas, and + benchmark realistic tenant sizes/concurrency. The sanctions + transport's fixed HTTPS/redirect allowlist is not a demonstrated arbitrary-URL + issue, but migration to Core's pinned egress transport remains desirable. + +Operational compatibility: tenant keys relying on accidental system/wildcard +permissions must be corrected rather than weakening the guard. Extreme sparse +spreadsheets, overly deep/long archive paths and oversized padding intermediates +can now fail early with diagnostics. No stored documents or configurations were +deleted or silently migrated. diff --git a/packages/govoplan-meta/pyproject.toml b/packages/govoplan-meta/pyproject.toml index 8a751ab..f566761 100644 --- a/packages/govoplan-meta/pyproject.toml +++ b/packages/govoplan-meta/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "govoplan-core[server]==0.1.45", "govoplan-tenancy==0.1.22", "govoplan-organizations==0.1.21", - "govoplan-identity==0.1.20", + "govoplan-identity==0.1.21", "govoplan-idm==0.1.25", "govoplan-access==0.1.25", "govoplan-admin==0.1.23", @@ -31,7 +31,7 @@ dependencies = [ [project.optional-dependencies] full = [ "govoplan-addresses==0.1.22", - "govoplan-approvals==0.1.20", + "govoplan-approvals==0.1.21", "govoplan-assets==0.1.20", "govoplan-booking==0.1.20", "govoplan-cases==0.1.24", @@ -45,30 +45,30 @@ full = [ "govoplan-decisions==0.1.19", "govoplan-dist-lists==0.1.21", "govoplan-dms==0.1.20", - "govoplan-encryption==0.1.19", + "govoplan-encryption==0.1.20", "govoplan-erp==0.1.20", "govoplan-evaluation==0.1.20", "govoplan-facilities==0.1.20", "govoplan-fit-connect==0.1.20", - "govoplan-forms==0.1.22", - "govoplan-forms-runtime==0.1.20", + "govoplan-forms==0.1.23", + "govoplan-forms-runtime==0.1.21", "govoplan-grants==0.1.20", "govoplan-helpdesk==0.1.21", - "govoplan-identity-trust==0.1.20", + "govoplan-identity-trust==0.1.21", "govoplan-inspections==0.1.20", "govoplan-learning==0.1.20", "govoplan-mandates==0.1.19", "govoplan-notifications==0.1.20", "govoplan-parties==0.1.19", - "govoplan-payments==0.1.21", + "govoplan-payments==0.1.22", "govoplan-permits==0.1.20", "govoplan-poll==0.1.20", - "govoplan-portal==0.1.21", - "govoplan-postbox==0.1.22", + "govoplan-portal==0.1.22", + "govoplan-postbox==0.1.23", "govoplan-procurement==0.1.20", - "govoplan-projects==0.1.19", - "govoplan-quick-access==0.1.20", - "govoplan-records==0.1.23", + "govoplan-projects==0.1.20", + "govoplan-quick-access==0.1.21", + "govoplan-records==0.1.24", "govoplan-reporting==0.1.21", "govoplan-resources==0.1.20", "govoplan-rest==0.1.19", diff --git a/requirements-release.txt b/requirements-release.txt index 7437e82..6244011 100644 --- a/requirements-release.txt +++ b/requirements-release.txt @@ -3,7 +3,7 @@ ../govoplan-core[server] govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.22 govoplan-organizations @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git@v0.1.21 -govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.20 +govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.21 govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.25 govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.25 govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.23 diff --git a/tests/test_webui_package_facades.py b/tests/test_webui_package_facades.py new file mode 100644 index 0000000..33118e7 --- /dev/null +++ b/tests/test_webui_package_facades.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +import json +from pathlib import Path +import runpy +import tempfile +import unittest + + +META_ROOT = Path(__file__).resolve().parents[1] +CHECK = runpy.run_path(str(META_ROOT / "tools/checks/check-webui-package-facades.py")) + + +class WebuiPackageFacadeTests(unittest.TestCase): + def setUp(self) -> None: + self.temporary = tempfile.TemporaryDirectory() + self.addCleanup(self.temporary.cleanup) + self.repository = Path(self.temporary.name) / "govoplan-example" + source = self.repository / "webui/src" + source.mkdir(parents=True) + (source / "index.ts").write_text("export {};\n", encoding="utf-8") + (source / "styles.css").write_text(":root {}\n", encoding="utf-8") + self.webui = { + "name": "@govoplan/example-webui", "version": "0.1.2", "type": "module", + "main": "src/index.ts", + "exports": {".": {"import": "./src/index.ts"}, "./styles.css": "./src/styles.css"}, + "peerDependencies": {"@govoplan/core-webui": "^0.1.45"}, + "peerDependenciesMeta": {"@govoplan/core-webui": {"optional": True}}, + } + self.root = {**self.webui, **{ + field: CHECK["prefixed_entries"](self.webui[field]) + for field in CHECK["ENTRY_FIELDS"] if field in self.webui + }} + self.write_manifests() + + def write_manifests(self) -> None: + (self.repository / "webui/package.json").write_text(json.dumps(self.webui), encoding="utf-8") + (self.repository / "package.json").write_text(json.dumps(self.root), encoding="utf-8") + + def issues(self) -> list[str]: + return CHECK["facade_issues"](self.repository, package_name="@govoplan/example-webui") + + def test_matching_conditional_and_css_entries_are_accepted(self) -> None: + self.assertEqual([], self.issues()) + + def test_missing_root_or_generic_package_is_rejected(self) -> None: + (self.repository / "package.json").unlink() + self.assertIn("cannot read", " ".join(self.issues())) + self.root = {"name": "@govoplan/example", "version": "0.1.2"} + self.write_manifests() + self.assertIn("root name differs", " ".join(self.issues())) + self.assertIn("no WebUI entry point", " ".join(self.issues())) + + def test_peer_drift_or_missing_entry_is_rejected(self) -> None: + self.root["peerDependencies"] = {"@govoplan/core-webui": "^9.0.0"} + self.write_manifests() + self.assertIn("peerDependencies differs", " ".join(self.issues())) + (self.repository / "webui/src/styles.css").unlink() + self.assertIn("missing exports entry", " ".join(self.issues())) + + def test_entry_cannot_escape_webui_even_if_it_exists(self) -> None: + (self.repository / "outside.ts").write_text("export {};\n", encoding="utf-8") + self.root["main"] = "webui/../outside.ts" + self.write_manifests() + self.assertIn("escapes webui/", " ".join(self.issues())) + + def test_release_composition_checks_only_declared_module_sources(self) -> None: + core = self.repository.parent / "govoplan-core/webui" + core.mkdir(parents=True) + (core / "package.release.json").write_text(json.dumps({"dependencies": { + "react": "19.2.7", + "@govoplan/example-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-example.git#v0.1.2", + }}), encoding="utf-8") + self.assertEqual((1, []), CHECK["check_composition"](self.repository.parent)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/checks/check-focused.sh b/tools/checks/check-focused.sh index 1ab465e..ea75ff0 100644 --- a/tools/checks/check-focused.sh +++ b/tools/checks/check-focused.sh @@ -47,6 +47,8 @@ cd "$META_ROOT" "$PYTHON" tools/inventory/platform-interface-inventory.py --strict-declarations --strict-endpoints "$PYTHON" tools/repo/sync-module-package-workflows.py --check "$PYTHON" tools/release/generate-developer-meta-package.py --check +"$PYTHON" tools/checks/check-webui-package-facades.py +"$PYTHON" -m unittest tests.test_webui_package_facades "$PYTHON" -m unittest tests.test_module_package_workflows tests.test_package_registry_release "$PYTHON" -m unittest tests.test_deployment_installer "$PYTHON" -m unittest tests.test_capability_fit_evidence diff --git a/tools/checks/check-webui-package-facades.py b/tools/checks/check-webui-package-facades.py new file mode 100644 index 0000000..96521f5 --- /dev/null +++ b/tools/checks/check-webui-package-facades.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +"""Check that release Git dependencies expose their owning WebUI package.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import re + + +META_ROOT = Path(__file__).resolve().parents[2] +GIT_REPOSITORY = re.compile(r"/(govoplan-[a-z0-9-]+)\.git#v[^/]+$") +PARITY_FIELDS = ( + "name", "version", "type", "dependencies", "optionalDependencies", + "peerDependencies", "peerDependenciesMeta", +) +ENTRY_FIELDS = ("main", "module", "types", "exports") + + +def prefixed_entries(value: object) -> object: + if isinstance(value, str): + return "./webui/" + value[2:] if value.startswith("./") else "webui/" + value + if isinstance(value, dict): + return {key: prefixed_entries(item) for key, item in value.items()} + if isinstance(value, list): + return [prefixed_entries(item) for item in value] + return value + + +def entry_paths(value: object) -> list[str]: + if isinstance(value, str): + return [value] + if isinstance(value, dict): + return [path for item in value.values() for path in entry_paths(item)] + if isinstance(value, list): + return [path for item in value for path in entry_paths(item)] + return [] + + +def facade_issues(repository: Path, *, package_name: str) -> list[str]: + issues: list[str] = [] + try: + root = json.loads((repository / "package.json").read_text(encoding="utf-8")) + webui = json.loads((repository / "webui/package.json").read_text(encoding="utf-8")) + except (OSError, ValueError) as exc: + return [f"{repository.name}: cannot read package facades: {exc}"] + if not isinstance(root, dict) or not isinstance(webui, dict): + return [f"{repository.name}: package manifests must be JSON objects"] + if webui.get("name") != package_name: + issues.append(f"{repository.name}: WebUI name does not match {package_name}") + for field in PARITY_FIELDS: + if root.get(field) != webui.get(field): + issues.append(f"{repository.name}: root {field} differs from owning WebUI package") + for field in ENTRY_FIELDS: + expected = prefixed_entries(webui.get(field)) + if root.get(field) != expected: + issues.append(f"{repository.name}: root {field} must target the corresponding webui/ entry") + for entry in entry_paths(root.get(field)): + path = repository / entry + if not path.resolve().is_relative_to((repository / "webui").resolve()): + issues.append(f"{repository.name}: {field} entry escapes webui/: {entry}") + elif "*" not in entry and not path.is_file(): + issues.append(f"{repository.name}: missing {field} entry: {entry}") + if not root.get("exports") and not root.get("main"): + issues.append(f"{repository.name}: root package has no WebUI entry point") + return issues + + +def check_composition(workspace: Path, *, core_root: Path | None = None) -> tuple[int, list[str]]: + core = core_root or workspace / "govoplan-core" + release = json.loads((core / "webui/package.release.json").read_text(encoding="utf-8")) + checked = 0 + issues: list[str] = [] + for name, reference in release.get("dependencies", {}).items(): + if not name.startswith("@govoplan/"): + continue + match = GIT_REPOSITORY.search(reference) if isinstance(reference, str) else None + if match is None: + issues.append(f"{name}: release dependency is not a versioned GovOPlaN Git source") + continue + checked += 1 + issues.extend(facade_issues(workspace / match.group(1), package_name=name)) + return checked, issues + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--workspace-root", type=Path, default=META_ROOT.parent) + parser.add_argument("--core-root", type=Path) + args = parser.parse_args() + checked, issues = check_composition(args.workspace_root, core_root=args.core_root) + if issues: + print("\n".join(issues)) + return 1 + print(f"Release WebUI package facade checks passed for {checked} Git dependencies") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())