feat(security): isolate bounded work and support required auth actions

This commit is contained in:
2026-09-08 07:47:17 +02:00
parent a6d056a3df
commit dc1f244f17
23 changed files with 1628 additions and 21 deletions
+81
View File
@@ -0,0 +1,81 @@
# Disposable resource-bounded operations
`security.bounded_process.run_bounded_operation` runs a trusted, importable,
module-level `bytes -> bytes` function in a fresh interpreter. Core owns the
process lifecycle, not the business parser. Owners retain authorization,
sessions, provider reads, idempotency and persistence in the parent and pass
only explicit bounded data. Never accept the operation, module or source path
from a client. Use `security.worker_payload` for typed values; it does not use
pickle, arbitrary constructors or JSON object hooks.
The runner requires POSIX process groups, `waitid(WNOWAIT)` and resource limits.
Unsupported controls fail closed; there is no in-process fallback. The child
uses `-I -B`, a fixed minimal environment, `/` as working directory, closed
inherited descriptors and a new process session. Limits are installed before
the owning module is imported. Installed dependencies must support isolated
Python imports; development `PYTHONPATH` alone is insufficient.
`ProcessLimits` specifies wall-clock seconds (including child startup), CPU
seconds, virtual address space, input/output pipe bytes and maximum regular-file
size. Defaults are 10 seconds wall/CPU, 256 MiB address space, 8 MiB input and
output, and no regular-file output. Wall/CPU limits are at most 600 seconds,
memory 64 MiB8 GiB, pipe limits 1 byte256 MiB, and file size 02 GiB. Owners
must document their tighter functional limits; a transport cap does not replace
row, archive expansion, item-count or artifact limits.
Admission is non-queuing. `GOVOPLAN_ISOLATED_PROCESS_CONCURRENCY` defaults to 1
(range 116) and applies across these operations **within each API/worker
process**. Multiply capacity and memory budgets by the number of API/worker
processes when sizing an installation. This is not a fleet-wide semaphore,
cgroup quota, filesystem/network sandbox or permission to run arbitrary code.
`RLIMIT_FSIZE` is per file, not a total disk quota. Owners creating staged files
must enforce cumulative quotas and clean up their own private directories.
Owners preparing bounded local snapshots can enter
`bounded_operation_admission()` before preparation and pass its token as
`admission=` to the runner. This reuses shared capacity rather than reserving a
second slot. Tokens belong to their active context, thread and process; expired,
cross-thread and overlapping reuse fail. Preparation exceptions release the
slot without launching a child. Never hold admission while waiting for a user;
parent-side preparation still requires explicit I/O and byte bounds.
The parent concurrently drains stdout/stderr while writing input. Output is
bounded during reading, stderr is discarded and capped at 64 KiB, and raw child
tracebacks are never returned. Every success, exception, timeout, cancellation
and callback failure kills the owned process group before reaping its leader,
including descendants which close their inherited pipes. A module-level child
handler must return bytes; it must not print logs/progress to stdout.
The optional `cancelled` callback runs in the parent at most roughly every
50 ms while waiting. It must be fast and must not return an awaitable. It may
also service a module-owned bounded progress protocol; exceptions terminate
the child and propagate. There is no fabricated progress for killed work.
`ProcessBudgetError.code` distinguishes busy, cancelled, timeout, CPU, memory,
input/output limits, unavailable controls and worker failure. Owners map these
to their existing structured diagnostics and recovery semantics.
The private typed-data codec supports null, booleans, strings, bytes, integers,
floats, Decimal, UUID, date/time/datetime, lists, tuples and string-keyed maps.
It rejects unsupported objects, malformed/trailing bytes, duplicate keys,
excess depth (64) and node counts (1,000,000). Operation DTOs remain owner
contracts and require owner validation. Do not persist this private wire format
or use it as a public API.
Tests use real child processes for catastrophic regex, memory exhaustion,
noisy output, exact limits, cancellation, closed-pipe hangs and descendant
cleanup. These are local process regression tests, not production concurrent
load certification. Operators still need target Linux/cgroup, cancellation,
worker-count, memory and disk-quota evidence before raising concurrency.
## Deutsche Betriebszusammenfassung
Rechenintensive, vertrauenswürdige Moduloperationen laufen in einem frischen
Prozess mit harten Laufzeit-, CPU-, Speicher- und Ausgabegrenzen. Berechtigungen,
Sitzungen, Zugangsdaten und Datenbankänderungen bleiben im Hauptprozess. Fehlende
Betriebssystemkontrollen führen zu einer Diagnose, nicht zu ungeschützter
Ausführung. `GOVOPLAN_ISOLATED_PROCESS_CONCURRENCY` begrenzt die gemeinsame
Zulassung je API-/Worker-Prozess, standardmäßig auf 1. Mehrere Prozesse haben
jeweils eigene Grenzen; systemweite Speicher- und Festplattenquoten müssen
Betreiber zusätzlich konfigurieren und auf der Zielinstallation prüfen. Die
Schnittstelle ist keine Sandbox für beliebigen Code. Modul-Dokumentation nennt
die jeweiligen fachlichen Grenzen, Fortschritts- und Wiederholungsregeln.
+2
View File
@@ -13,3 +13,5 @@ tools/checks/security-audit/run.sh --mode full --scope govoplan
Canonical documentation:
- `/mnt/DATA/git/govoplan/docs/operations/SECURITY_AUDIT.md`
Implementation contract: [disposable resource-bounded operations](BOUNDED_PROCESS_CONTRACT.md).