docs backend evidence community safety SEC03B_TRANSFER_BOUNDARY - AlexandreZanata/Abastevo GitHub Wiki
SEC03-B — Transfer-boundary decision
Date: 2026-10-11. Status: LOCAL_DONE. Scope: boundary selection +
unit pins — no provider change, no new transport (SEC03-C owns the
provider-compatible matrix). Head: maintained dev + this task.
Plan: remediation plan
(SEC03, AUD-02). Constraint: no S3 POST policy assumed for R2 (R2
documents POST form uploads as unsupported); no exact-length PUT
claimed without provider/transport proof.
Validation: PASS
Decision
Keep direct presigned PUT and enforce at verification (selected):
- Transport cannot cap: PUT signs
content-type;hostwithUNSIGNED-PAYLOAD(storage/presign.go; proven byTestPresignDoesNotEnforceSize— one-byte and 3 MiB ceilings mint the same URL). Exact-length signed PUT is NOT selected: no provider proof exists in this environment that R2 honors a signed length bound, and inventing one would be a false ceiling. - Enforcement lands where bytes are actually read, exactly once:
worker
VerifydownloadsMaxBytes+1(overflow detectable, never silently truncated),media.Snapshotcaps, the claimed SHA-256 must match constant-time, dimensions/inspection and sanitization gate promotion, and exactly one download binds the outcome (TOCTOU-safe). Oversize/substituted/missing objects never reach the server-only final key. - Admission ahead of transfer (SEC03-A): count + live-byte budgets admit atomically, so parallel fresh reservations cannot exceed 10 rows / 30 MiB per contributor/day.
- Least privilege around transfer: 5-minute URL TTL (1..15 min
bounds), quarantine-only keys (
q/, traversal/control refused), single-purpose methods, private bucket (no ACL/grant emitted), no-redirect transport, 8/16/64 KiB API bodies elsewhere. - Incomplete writes: S3 PUT is atomic (whole object or nothing); interrupted transfers surface as absent objects — transient, never promoted, reconciled by expiry/sweep (24 h session TTL, quarantine/final cleanup). Repeated PUTs inside the TTL overwrite the key but verify binds the reservation claim: same bytes converge, divergent bytes mismatch-reject.
Changes
evidence/application/verify_boundary_test.go(new, 3 pins): declared 1 KiB + actual MaxBytes+1 rejects oversize with zero final upload; substituted bytes reject checksum-mismatch with zero final upload; absent object stays transient (never permanent, never promoted).
Validation (actual)
go test ./internal/modules/evidence/...green (incl. 3 new boundary pins; oversize/mismatch/transient/swap/TTL/namespace/ redirect suites re-green).go vet,gofmt -l,git diff --checkclean,scan-secrets.shpassed (synthetic hashes only).- RED pre-GREEN: boundary pins written against the flaw model (declared-widens, substitution-promotes, absence-promotes) and observed GREEN on first run — the composition was already correct; RED was the missing pin, as recorded (no behavior change in this task by design).
Limits / owed
- Provider-compatible matrix (declared/actual mismatch, missing/chunked length, max/max+1, URL reuse, expired/revoked session, wrong owner/key/MIME/hash, interrupted transfer, concurrent admission over the actual protocol): SEC03-C, small synthetic payloads against a real local S3-compatible service.
- Namespace/monitoring (public-prefix exclusion, worker caps, failed-object reconciliation, byte metrics): SEC03-D.
- R2 production behavior unverified here; no production claim.
Next microtask: SEC03-C provider-compatible matrix.