52 lines
4.0 KiB
Markdown
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.
|