spec: pin FIELD_FILE_ATTACHMENTS (0x05) value shape

§5.9.7 — FIELD_FILE_ATTACHMENTS is a list of [filename, file_bytes]
pairs (multiple attachments per message). Confirmed from upstream
Sideband source: core.py builds `fields[FIELD_FILE_ATTACHMENTS] =
[attachment]` with `attachment = [filename, filedata]`; ui/messages.py
reads `attachment[0]`/`attachment[1]` on receive and strips `../`
from the filename.

Removed FIELD_FILE_ATTACHMENTS from the §5.9 UNVERIFIED list and
added a §5.9.7 pointer to the field table. Documents the
sender-controlled-filename sanitisation requirement.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Rob 2026-05-18 12:40:05 -04:00
commit fd2951be82

35
SPEC.md
View file

@ -963,7 +963,7 @@ Sender and receiver agree on these keys; each value's structure is field-specifi
| `0x02` | `FIELD_TELEMETRY` | A single telemetry snapshot (Sideband telemetry — see Sideband for the inner format; LXMF is opaque to the contents). |
| `0x03` | `FIELD_TELEMETRY_STREAM` | A list of telemetry snapshots (history flush). |
| `0x04` | `FIELD_ICON_APPEARANCE` | Sender-supplied avatar / appearance hint. |
| `0x05` | `FIELD_FILE_ATTACHMENTS` | A list of attached files (multiple attachments per message). |
| `0x05` | `FIELD_FILE_ATTACHMENTS` | A list of attached files (multiple attachments per message). See §5.9.7 for the wire shape. |
| `0x06` | `FIELD_IMAGE` | Single embedded image — `[extension_string, image_bytes]`. See §5.9.2 for the wire shape. |
| `0x07` | `FIELD_AUDIO` | Single embedded audio clip — `[mode_byte, audio_bytes]`. Mode byte chooses the codec; see §5.9.3. |
| `0x08` | `FIELD_THREAD` | Conversation thread ID (links related messages). |
@ -980,7 +980,7 @@ Sender and receiver agree on these keys; each value's structure is field-specifi
| `0xFE` | `FIELD_NON_SPECIFIC` | Development / unstructured payload — not for production. |
| `0xFF` | `FIELD_DEBUG` | Debug payload — not for production. |
> ⚠️ **UNVERIFIED:** the byte-level shape of `FIELD_EMBEDDED_LXMS`, `FIELD_TELEMETRY*`, `FIELD_FILE_ATTACHMENTS`, `FIELD_COMMANDS`, `FIELD_RESULTS`, `FIELD_GROUP`, `FIELD_EVENT`, and `FIELD_RNR_REFS` is not described here because no test vectors have been captured against upstream Sideband emissions for these. The constants are verified (see `tools/verify_lxmf_fields.py`) but the value structures are application-defined and not pinned by LXMF itself. Future PRs should add per-field byte layouts as test vectors arrive.
> ⚠️ **UNVERIFIED:** the byte-level shape of `FIELD_EMBEDDED_LXMS`, `FIELD_TELEMETRY*`, `FIELD_COMMANDS`, `FIELD_RESULTS`, `FIELD_GROUP`, `FIELD_EVENT`, and `FIELD_RNR_REFS` is not described here because no test vectors have been captured against upstream Sideband emissions for these. The constants are verified (see `tools/verify_lxmf_fields.py`) but the value structures are application-defined and not pinned by LXMF itself. Future PRs should add per-field byte layouts as test vectors arrive. (`FIELD_FILE_ATTACHMENTS` was on this list until 2026-05-18 — its shape is now documented in §5.9.7 from upstream Sideband source.)
#### 5.9.2 `FIELD_IMAGE` (`0x06`) value shape
@ -1062,6 +1062,37 @@ For announce-level capability negotiation:
|---|---|---|
| `0x00` | `SF_COMPRESSION` | Sender supports compressed message bodies (see §10.12) |
#### 5.9.7 `FIELD_FILE_ATTACHMENTS` (`0x05`) value shape
```
fields[0x05] = [ [filename(str-or-bytes), file_bytes(bytes)], ... ]
```
A list of attachments — one LXMF message may carry more than one
file. Each attachment is itself a 2-element list: element `[0]` is the
file name, element `[1]` is the raw file content. As with `FIELD_IMAGE`
(§5.9.2) the file name may arrive as msgpack `str` or `bin` depending
on the encoder — receivers must tolerate both (see §9.3).
The file name is **sender-controlled and untrusted**. Upstream Sideband
strips `../` from it on receive (`sbapp/ui/messages.py`:
`filename = str(attachment[0]).replace("../", "")`); receivers MUST
sanitise it — reject or strip path separators, `..` segments and
control characters — before display or save, and never let it
influence a write path. The extension is likewise untrusted: do not
auto-open or auto-execute an attachment based on its claimed type.
Files larger than a single Reticulum DATA packet are delivered as a
Resource over a Link, identically to large `FIELD_IMAGE` payloads
(§10).
Source: `markqvist/Sideband` `sbapp/sideband/core.py`
(`fields[LXMF.FIELD_FILE_ATTACHMENTS] = [attachment]`, where each
`attachment = [filename, filedata]`) and `sbapp/ui/messages.py`
(receive side indexes `attachment[0]` filename / `attachment[1]`
bytes). Confirmed from upstream source 2026-05-18; a captured wire
test vector would further pin the msgpack `str`-vs-`bin` choice.
### 5.10 Source
`LXMF/LXMessage.py` for pack/unpack; `LXMF/LXMF.py` for the app_data extraction helpers and the field/audio/renderer constants enumerated in §5.9; `LXMF/LXStamper.py` for stamps; `LXMF/LXMRouter.py` for receive-side stamp/ticket dispatch and propagation handlers; `LXMF/LXMPeer.py` for the propagation peer-to-peer state machine.