fix(cv): isolate document parsing

Run untrusted document decoders in a secret-free, resource-bounded child process and terminate its process tree on deadline. Harden the production container and enforce parser and lint gates in CI.
This commit is contained in:
cesnimda
2026-08-30 11:12:25 +02:00
parent a8bf505ce5
commit 19c5251612
13 changed files with 677 additions and 176 deletions
@@ -0,0 +1,46 @@
# SEC-007 parser isolation verification
## Implemented boundary
- The FastAPI web process reads at most 5 MiB and performs cheap extension, signature and DOCX-container preflight checks.
- TXT, Markdown, PDF, DOCX and image decoding runs in `parser_child.py`, never in the web process.
- Only one parser child is admitted at a time; excess work receives a stable busy response instead of building an in-memory parser queue.
- The child receives a minimal environment without AI provider keys or the service token.
- Linux applies CPU, address-space, output-file, open-file and core-dump limits before importing parser libraries.
- The parent enforces a 25-second deadline and terminates the process group/tree, including OCR descendants.
- Per-request work uses an owner-private temporary directory. Old `work-*` directories are reconciled without following symlinks or removing unrelated paths.
- The backend has no PDF/DOCX/image fallback. Stable messages replace unexpected internal exceptions and paths.
## Container boundary
The production Compose definition resolves the AI service with:
- non-root Dockerfile user;
- read-only root filesystem;
- all Linux capabilities dropped;
- `no-new-privileges`;
- 96 PID, 2 CPU and 2 GiB memory limits;
- 768 MiB `noexec,nosuid,nodev` `/tmp` tmpfs;
- one writable named volume only for the Hugging Face model cache.
## Local proof
- Parser tests: 37/37 passed.
- Focused backend extraction/operation tests: 51/51 passed.
- Full backend: 719/719 passed.
- Python compilation: `app.py` and `parser_child.py` passed.
- Compose config: valid with all intended controls present.
- Timeout regression spawns a harmless sleeping descendant and proves the process tree is gone after the deadline.
- Environment regression proves `GEMINI_API_KEY`, `GROQ_API_KEY` and `AI_SERVICE_TOKEN` are absent from the child.
## Remaining external proof
Docker Desktop's Linux daemon is offline, so the image could not be built or run. Before activation:
1. Build the hash-locked image and record its digest.
2. Confirm the runtime UID is non-root and the root filesystem rejects writes.
3. Inspect the applied CPU/memory/PID/capability/tmpfs settings.
4. Exercise benign TXT, Markdown, PDF, DOCX and image extraction within the configured budgets.
5. Measure peak parent/child memory and OCR duration; lower or evidence any limit increase.
6. Run the authenticated browser upload/review journey with synthetic files.
7. Canary in production only after explicit authorization; disable CV import rather than removing the boundary if the budget is insufficient.