Security
Copy MarkdownUpstream F3 is a research prototype. This library is experimental. Treat every .f3 file as untrusted input.
What Stage 0 enforces
pread/3returns at most the requested length, and never more bytes than remain after the offset captured at open. A read at or past the end returns an empty binary. It does not raise.F3.Errormessages and context carry a path, an operation, or an Erlang reason atom. They do not carry file bytes. Contract tests use a marker string and reject it in the error and ininspect/1of a memory handle.InspectforF3.File,F3.IO.Local, andF3.IO.Memoryprints the origin, size, and closed flag. It does not print the buffer.F3.stream/2returns before any read.- No code fetches a URL from metadata. The schema's
lib_urlsfield is not interpreted. - No Wasm module is compiled or executed. The native scaffold has no file or network API.
- There is no global size registry. The caller passes the maximum read length.
What Stage 1 enforces
- The postscript is read as exactly 32 bytes. A shorter file is
:truncated. Magic other thanF3is:invalid_magic. A version other than 0.1 is:unsupported_version. - Metadata size and footer size are checked against the file length, against each other, and against
max_metadata_bytes:before any buffer of that size is allocated. The default cap is 16 MiB. An over-cap claim is:resource_limit. The cap is an argument toschema/1,metadata/1, andinspect/2. - Footer compression other than uncompressed is
:unsupported_encoding. Nothing is decompressed. - Checksum type
0is reported as:xxhash. Any other byte is{:unknown, byte}. The library does not panic and does not recompute either checksum. - FlatBuffers offsets are checked against the buffer. A cyclic offset hits the visit cap and returns
:corrupt_metadata. Column-metadata slices must sit strictly before the footer, inside the metadata span. A pointer into the footer or the postscript is:corrupt_metadata. - Optional Wasm sections are names and file offsets. The bytes are not read,
lib_urlsis not fetched, and nothing is executed. - Error messages do not include file bytes. A corrupt-footer test checks that a column name from the file does not appear in the message.
- Metadata inspection does not decode encoding units.
What Stage 2 enforces
read/2andstream/2return:native_unavailablewhen the NIF was not compiled. They do not scan payloads in that build.- The decoder NIF is
DirtyCpu. It does not linkfff-pocand it does not install a global allocator. A panic or a corrupt unit becomes{:error, %F3.Error{}}. It does not kill the caller. - Zstd and Lz4 are decompressed inside that NIF. The result is an owned Elixir term. Arrow buffers do not cross the boundary.
CUSTOM_WASMis:unsupported_encodingunless the caller passeswasm: :sandboxor a matching extension. A shared dictionary is:unsupported_encoding.- A chunk checksum is recomputed only when the footer stored one. The eight fixtures store none. Postscript checksums stay reported and unverified.
- Error messages from the NIF are stripped of control characters and capped at 180 characters. They do not include the payload.
What Stage 3 enforces
wasm:defaults to:off.CUSTOM_WASMis:unsupported_encoding. TheWASMBinariessection is not read. Metadata inspection never compiles a module.:sandboxis the only other accepted value. The host is Wasmtime 28 on the dirty CPU scheduler. A module is at most 1 MiB unlesswasm_module_bytes:raises it, and never above 16 MiB. The pinned built-in module is 10_661_135 bytes, which is why the ceiling is 16 MiB rather than 8 MiB. Linear memory is at most 8 MiB. Fuel is 5_000_000 instructions. Upstream sets no fuel. Exhaustion is:resource_limit. An omittedwasm_idis the FlatBuffers default 0.- WASI stdout and stderr are sinks. There is no preopened directory and the process environment is not inherited.
path_openand socket imports are refused before instantiation. lib_urlsare not parsed and not fetched. Metadata inspection does not open a socket. Stage 5 adds an opt-in HTTP source that is not used for a local file.- A registered
F3.Extensionis this library's policy. An error from it is returned as-is and does not fall through into Wasm.
What Stage 5 enforces
- A remote read is opt-in:
source: {:http, url}orsource: {:http, url, opts}. Opening a local path does not contact the network.lib_urlsare not turned into requests. - The client sets
autoredirect: false. Status 301, 302, 303, 307, and 308 are:iowith reason:redirect. - A body shorter than the requested range is
:truncated. A status other than 200 or 206 is:io, except 404, which is:not_found. A timeout, or a body larger than the request, is:resource_limit. InspectforF3.IO.HTTPprints the url and the size. It does not print headers or a signing secret.%F3.Error{}does not store that secret.F3.Writerdoes not grow a remote path.close/1is still a local rename.
What later stages must keep
- Embedded Wasm stays off unless the caller opts in. A module URL in the file is data, not a download.
- A NIF must not install a process-global allocator and must not run long decompression on a normal scheduler. The Stage 2 decoder stays on the dirty CPU scheduler.
- Errors continue to omit payloads and secrets.
- Postscript checksums stay unverified. Chunk checksums are checked only when the footer field is present.
Malformed-input tests cover a truncated tail, a bad magic, a bad version, sizes past the file, a size over the cap, an unknown checksum byte, a corrupt footer root, a corrupt vtable, and a corrupt column-metadata pointer. A fixed-seed corpus of random binaries must return {:error, %F3.Error{}}. This is not a fuzzer.