208 lines
15 KiB
Markdown
208 lines
15 KiB
Markdown
# 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.
|
|
|
|
## Post-release follow-up — 2026-09-08
|
|
|
|
The findings and scanner counts above describe the original audit snapshot.
|
|
The following source fixes are subsequent to the frozen `0.1.45` composition;
|
|
they do not change its immutable tags or published package bytes.
|
|
|
|
- [Xrechnung #2](https://git.add-ideas.de/GovOPlaN/govoplan-xrechnung/issues/2)
|
|
now enforces the existing shared 2 MiB stdout/stderr limit during execution
|
|
and kills/reaps the direct validator on overflow, timeout or cancellation.
|
|
Report reads are bounded to 16 MiB plus one probe byte before interpretation.
|
|
The 30-test module suite passes; noisy-child and report-read regressions were
|
|
also demonstrated to fail against the previous source. Owning EN/DE static
|
|
documentation is updated. POSIX pipe capture is required; disk quotas,
|
|
descendant isolation and process-level CPU/memory limits remain separate work.
|
|
- [Meta #54](https://git.add-ideas.de/GovOPlaN/govoplan/issues/54) now preserves
|
|
the last command's failure status after exhausted installer retries. Twelve
|
|
isolated stage/scenario combinations cover every retry call site, success,
|
|
backoff and caller termination under `set -e`. The test is included in the
|
|
focused checks and installer CI. See the bilingual
|
|
[installer retry note](../operations/WEBUI_RELEASE_DEPENDENCY_RETRIES.md).
|
|
|
|
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.
|