Files
govoplan/docs/security/SECURITY_PERFORMANCE_REVIEW_2026-09-08.md
T
zemion 58d320d9b3
Dependency Audit / dependency-audit (push) Successful in 1m51s
Deployment Installer / deployment-installer (push) Successful in 8s
Security Audit / security-audit (push) Successful in 12m32s
Harden source release preparation and record verified security follow-up
2026-09-08 08:04:12 +02:00

15 KiB

Security and performance review — 8 September 2026

Coordinated status: Core #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 and the 0.1.45 release notes.

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.
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, 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, 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, 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, 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, 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, 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, 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.
  • 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, 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 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.
  • 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: 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: 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: enforce the existing 2 MiB limit while draining stdout/stderr, not afterwards.
  4. Workflow revision batching/history projection — Workflow Engine #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 findings tracked in Meta #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 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 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.

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