# Fileserver security model ## The rule everything else follows Uploaded content is attacker-controlled. Every guard below exists because some part of the request — the filename, the declared media type, the range, the offset — is a value the caller chose. ## Path safety A client value never becomes a path. The physical key is server-generated, and `ContentKey`'s character class excludes `.` entirely, so no traversal or extension-shaped segment survives validation. `DefaultPhysicalPathResolver` is the only place an identifier becomes a `Path`, and it normalizes and re-checks containment after construction rather than trusting the input. Symlink refusal happens at open time, not only at construction. A parent directory can be replaced between the two, so a check that ran only at path-building time would be a race, not a guard. ## Filename handling `OriginalFilenamePolicy` strips path separators, NUL, quoting characters, and the colon — the last because on Windows it opens both a drive reference and an NTFS alternate data stream, so a name that keeps it is still path-shaped after the slashes are gone. Control characters and bidirectional overrides are removed, dot runs collapsed, reserved device names guarded, and the result is bounded in UTF-8 bytes. The sanitized name is display data. It is never used to build a key, and it reaches a header only through `ContentDispositionFactory`, which restricts the ASCII form and percent-encodes the UTF-8 form. ## Content type The client's `Content-Type` is stored as a claim. The verified type comes from the verification pipeline, and only the verified type is served. A claimed type that contradicts the content is quarantined rather than corrected. Scriptable types are never served inline, whatever the caller asked for: serving stored HTML or SVG inline from an upload origin is a stored cross-site scripting primitive. Every download also carries `X-Content-Type-Options: nosniff`. ## Verification precedence `REJECT > QUARANTINE > RETRY > ACCEPT`. A verifier that times out or throws is `RETRY`, never a silent pass, and an empty verifier chain answers `RETRY` rather than accepting. A file becomes publicly readable only after an `ACCEPT`. ## Range safety The range budget is enforced before content is opened, so a request naming many ranges is rejected without amplifying into storage work. An unsatisfiable range answers `416` with the real length and opens nothing. ## Authorization Every public operation calls the injected `FileAccessPolicy` before any quota reservation or storage mutation, so a denial leaves no record, no reservation, and no staging object. Startup refuses to run a production profile with an allow-all policy. ## Delegation `X-Accel-Redirect` is emitted only after authorization and the READY gate, and only for a full, unconditional response. The internal prefix must be an `internal` Nginx location; the front proxy also strips any client-supplied delegation header so a caller cannot name an internal object. ## Telemetry No metric label, span attribute, or audit record carries a file id, upload id, filename, path, or user id. Where correlation is needed the value is a keyed HMAC fingerprint — keyed because the identifier space is enumerable and an unkeyed digest of it is reversible by brute force. ## Ambiguous failures A failure whose operation may already have taken effect is reported as ambiguous and is never retryable. On a network filesystem a lost response is indistinguishable from a rejection at the socket level, so anything not provably safe is treated as ambiguous and sent to reconciliation.