Files
jobtrackingapp/docs/verification/sec-008-attachment-consistency.md
T

52 lines
4.0 KiB
Markdown

# SEC-008 attachment consistency verification
Updated: 2026-08-02
Status: `IMPLEMENTED — NOT VERIFIED`. Repository, real-SQLite integration and isolated HTTP checks pass. Browser and production checks remain blocked.
## Revalidated execution path
The former upload path copied files before validating the full batch and before the database commit; rename moved bytes before metadata commit; delete committed metadata before a best-effort file delete. These were confirmed JT-010 split-brain windows. Existing mitigations were owner-scoped queries, generated storage names, size/type/quota validation and derived attachment flags; there was no durable recovery state.
The implemented invariant uses the generated final path as the operation identity:
- `<final>.uploading` is a durable staged upload. Startup promotes it when its database row exists and purges it when no row exists.
- `<final>.deleting` is quarantined deletion data. Startup restores it when its row exists and purges it when no row exists.
- plain unknown files are counted and preserved; missing rows/files and unsafe stored paths are counted for review.
- rename changes display metadata only.
- attachment paths must remain under the configured root without child symlink/junction traversal.
Transaction failure cleanup occurs only after a confirmed rollback. An uncertain commit/rollback outcome preserves the suffix marker so restart reconciliation, rather than an unsafe guess, decides from durable database state.
## Automated evidence
| Check | Result |
|---|---|
| `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --no-restore --filter "FullyQualifiedName~AttachmentConsistencyTests|FullyQualifiedName~AttachmentFlagsRecomputeTests|FullyQualifiedName~AttachmentsControllerTests" --logger "console;verbosity=minimal"` | PASS — 20/20 |
| `dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --no-restore --logger "console;verbosity=minimal"` | PASS — 525/525 |
| `git diff --check` | PASS — no whitespace errors; only repository line-ending notices |
The real-SQLite tests cover invalid later files, cancelled copy, database rollback, upload-promotion failure and restart, delete-purge failure and restart, quarantined-delete restore, idempotent reconciliation, metadata-only rename with atomic flags, repeated same-name uploads, exact 10 MiB boundary, one-byte-over rejection, outside-root rejection, unknown-orphan preservation and two-user isolation. Failure injection uses no malicious documents.
## Runtime evidence
An isolated development API ran on `127.0.0.1:5305` with disposable synthetic SQLite data:
- User A login, `.txt` upload, list, download, metadata rename/purpose update and delete succeeded (`200/204` as applicable).
- User B received `404` for User A's attachment download and delete.
- the final owner list was empty after deletion.
- startup reconciliation completed before the service accepted requests.
- the exact API PID was stopped and port 5305 was confirmed closed.
Logs are `docs/audits/evidence/core-001-runtime/sec-008.stdout.log` and `sec-008.stderr.log`; the source fixture is `synthetic-attachment.txt`. They contain synthetic local data only.
## Unverified gates and rollback
- In-app browser access to localhost is administrator-policy blocked, so upload/rename/delete/refresh was not browser-tested.
- This Windows host denied creation of a disposable symbolic link; child reparse-point refusal was code-inspected but not executed. Outside-root traversal is tested.
- No production report-only orphan inventory, counter monitoring, multi-replica exercise or MariaDB runtime was performed.
- Periodic reconciliation is deliberately deferred; startup retry is the current recovery trigger.
- Entitlement quota behavior is owned by POL-001; the unchanged quota calculation was not reclassified as verified here.
Before rollback, reconcile or manually review all `.uploading` and `.deleting` markers. Never delete unknown plain orphans automatically. No database/configuration migration was introduced.