# 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.