Files
onenote-tools/docs/SECURITY.md

181 lines
10 KiB
Markdown

# Security model
## Trust boundary and local processing
Selected `.one`, `.onepkg`, and companion `.cab` files are untrusted. The main
thread validates the selection and combined size, reads the files into
`ArrayBuffer`s, and transfers them to a dedicated module worker. CAB entries
are extracted only into memory; archive paths are never written to the
filesystem.
The worker is a fault-containment and responsiveness boundary, not a security
sandbox. A successful open keeps its session alive for page/resource/export
requests. Clearing the file, choosing another file, or unmounting the app
terminates that worker; a failed open also terminates it. JavaScript garbage
collection does not promise immediate release or secure erasure.
The application does not:
- upload or transmit notebook bytes;
- use a notebook-processing application backend, upload proxy, telemetry,
analytics, remote fonts, or notebook-referenced external content fetching;
the optional same-origin Toolbox catalogue/manifest is application chrome
and receives no notebook data;
- persist source or derived content in LocalStorage, IndexedDB, Cache Storage,
cookies, or a service worker;
- execute notebook content or inject notebook HTML/SVG;
- use WebAssembly, a Rust runtime, or a native helper.
## Active parser defaults
The main thread accepts one `.one`, or one `.onepkg` plus its `.cab`
companions. It permits at most 256 selected parts, 512 MiB per file, and 512 MiB
combined before reading.
The OneStore parser applies these independent defaults per section/store:
| Resource | Default limit |
| ------------------------------------------------ | -------------------------: |
| Section bytes | 512 MiB |
| Transaction fragments / entries | 16,384 / 1,000,000 |
| File-node list fragments / nesting | 16,384 / 64 |
| File nodes | 1,000,000 |
| Objects | 500,000 |
| Properties per set / property nesting | 16,384 / 64 |
| Stream IDs | 1,000,000 |
| FSSHTTPB revision-chain entries | 16,384 |
| One property vector | 64 MiB |
| Desktop object-reference vector | 1,000,000 per vector |
| FSSHTTPB ExGUID/CellID references | 1,000,000 aggregate |
| Nested PropertySets per ObjectPropSet | 1,000,000 aggregate |
| File-data objects / one payload / total payloads | 10,000 / 256 MiB / 512 MiB |
| Content-graph depth | 256 |
| Content blocks / text runs / table cells | 100,000 each |
| Decoded ink points | 100,000 |
| Pages | 10,000 |
| Diagnostics | 10,000 |
| Retained rich/extracted text | 2,000,000 UTF-16 units |
Images are automatically previewable only when a bounded PNG IHDR or JPEG
marker/SOF inspection succeeds. The compressed payload must be at most 32 MiB,
each dimension at most 16,384 pixels, and the image at most 67,108,864 pixels.
JPEG inspection reads at most 1 MiB of markers and 4,096 marker records. Images
outside those limits stay inert and are available only through an explicit
download action.
The CAB/package path applies these defaults across a complete cabinet set:
| Resource | Default limit |
| ------------------------------------------- | -----------------: |
| One cabinet / complete set input | 512 MiB / 512 MiB |
| Cabinet parts | 256 |
| Folder segments / files | 1,024 / 10,000 |
| CFDATA fragments | 131,072 |
| CAB name bytes | 255 |
| Path characters / path segments | 4,096 / 64 |
| TOC construction operations / diagnostics | 1,000,000 / 10,000 |
| One extracted file | 256 MiB |
| One logical folder output | 512 MiB |
| Total folder output / extracted entry bytes | 512 MiB / 512 MiB |
| Logical-folder compression ratio | 200:1 |
| One MSZIP compressed block | 32 KiB + 12 B |
| Quantum window | 2 MiB |
| LZX window | 8 MiB |
| LZX/Quantum decode operations | 1,000,000,000 |
A CFDATA block may declare at most 32 KiB of output. Counts and offsets use
checked arithmetic and bounded readers. The CAB parser verifies supplied block
checksums; rejects overlapping/out-of-range data, traversal/absolute/control or
case-insensitive duplicate paths; and validates a complete cabinet set before
decoding. Set IDs, indexes, links, compression, split blocks, and spanning-file
records must agree. Reserve areas are private to each cabinet/folder and are
validated locally rather than compared across parts.
MSZIP checks `CK` framing and raw DEFLATE data, writes into a fixed caller
buffer with overflow detection, requires the declared output size, and keeps at
most 32 KiB of logical-folder history. LZX and Quantum validate their windows,
trees/models, blocks/frames, matches, output, trailer, and operation budgets.
Codec state is preserved across continued cabinet segments. Extraction checks
cancellation between data blocks and periodically yields to the worker loop.
Desktop OneStore traversal checks fragment/reference cycles, nesting,
object/property/reference counts, and byte ranges. FSSHTTPB parsing bounds every
declared stream body and compact integer, rejects invalid end markers and
non-zero package tails, checks aggregate reference/mapping counts and revision
cycles, and retains BLOB payloads as bounded source ranges. Fragment records
are range-checked, but packages containing them are rejected because
fragmented-element reconstruction is not implemented.
## Display and export budgets
Parser DTOs are projected through a smaller page-display budget before React
creates nodes: 5,000 blocks, depth 64, 10,000 text runs, 500,000 displayed text
units, 2,000 table rows, 5,000 table cells, 256 columns, 10,000 ink strokes,
50,000 ink points, and 4,096 characters per metadata label. Exceeding a display
budget produces an explicit truncation notice. Newlines remain inside text
nodes instead of multiplying DOM elements.
Navigation and inspectors separately expose at most 250 page links, 500
notebook nodes at depth 16, 200 package entries per page, and 100 diagnostics
per page. Titles, previews, package paths, diagnostic fields, and panel labels
are length-bounded; pagination/truncation remains explicit in the UI.
Export planning independently permits at most 10,000 sections, 100,000 pages,
1,000,000 blocks, depth 256, 100,000 diagnostics, 8,000,000 text units, and
500,000 ink points. One resource and all resources are capped at 64 MiB; one
standalone output or all uncompressed static-ZIP members are capped at 128 MiB.
Planning accounts for metadata and projected UTF-8 output before serialization,
then verifies actual encoded/member sizes. Resource views remain zero-copy until
the bounded serializer needs bytes.
## Rendering, links, and downloads
DTO strings are rendered as React text nodes. Notebook HTML, scripts, active
SVG, and attachment payloads are never injected. Only parsed ink coordinates
generate application-owned SVG path attributes. Hyperlinks require an explicit
click, are limited to `http`, `https`, or `mailto`, and use
`rel="noopener noreferrer"`.
Automatic image requests carry a distinct preview intent. The worker rejects a
non-image, unapproved media type, unsafe dimensions, or oversized payload before
allocating/copying its bytes to the main thread. Approved image bytes may cross
for automatic preview; attachment bytes and rejected image bytes cross only
after the user presses a download button.
Static export HTML is inert, contains no script or external fetch, escapes all
text/attributes, embeds only images that pass the same inspector, and
neutralizes potentially active attachment suffixes. Exports represent the
supported reader model; they do not write OneNote files.
## Known gaps
- There is no wall-clock deadline. Synchronous OneStore graph parsing cannot
observe cancellation until control returns; terminating the worker is the
current hard stop.
- Package opening can hold source cabinets, decompressed logical folders,
extracted entry views, and parsed DTOs at overlapping times. Byte/count limits
bound them, but do not guarantee a low peak heap.
- Section content limits apply per contained section rather than once to the
complete `.onepkg`; cabinet/TOC limits are aggregate for the package set.
- CAB checksums detect accidental corruption but do not authenticate a package.
- Format coverage is limited to the documented golden files and structures.
New producer/version claims require licensed regression fixtures and malformed
variants.
- The worker boundary reduces UI impact but is not a substitute for browser and
operating-system isolation. Open especially sensitive files in a dedicated
browser profile if that risk matters.
Planned hardening includes elapsed-time budgets, more cancellation checkpoints,
lower-peak-memory extraction, expanded malformed-input/fuzz corpora, fragmented
FSSHTTP reconstruction, and additional licensed OneNote producer fixtures.
## Fixtures and vulnerability reporting
Never commit private notebooks. Fixture provenance and redistribution terms
must be recorded in `tests/fixtures/README.md`.
Report suspected parser vulnerabilities privately to the repository owner
through the Gitea instance before publishing malicious fixtures. Include the
application version, browser, smallest reproducible input, diagnostic, and
observed resource use. Do not attach sensitive notebooks.