Core Safety Principles
- Validate before read. Every field is bounds-checked before access. No buffer read occurs without first confirming that sufficient bytes remain.
- Cap enforcement. All resource pools (commands, strings, blobs, total bytes) have configurable upper bounds. The builder refuses to exceed them.
- Structured error returns. Parsers and builders never throw exceptions into user code for format violations. Errors are returned as typed result objects.
- Alignment invariants. All section offsets and sizes are 4-byte aligned. Padding bytes are explicitly zeroed, never left as garbage.
- Deterministic failure. The same invalid input always produces the same error. No undefined behavior, no silent data corruption.
Binary Protocol Safety
Validate-Before-Read Pattern
TheBinaryReader class enforces bounds checking on every read operation:
ZrBinaryError with the exact byte offset and a description of how many bytes were needed vs. available.
Location: packages/core/src/binary/reader.ts
Writer Safety
TheBinaryWriter class applies the same pattern on the write side:
writeU32 and writeI32 are called only at 4-byte aligned offsets. Calling at a misaligned offset throws ZR_MISALIGNED.
Location: packages/core/src/binary/writer.ts
Binary Error Types
All binary-layer violations produce aZrBinaryError:
| Code | Meaning |
|---|---|
ZR_TRUNCATED | Attempted read beyond buffer bounds |
ZR_MISALIGNED | Offset violates 4-byte alignment requirement |
ZR_LIMIT | Operation exceeds configured capacity |
offset field records the byte position where the violation occurred, enabling precise diagnosis in hex dumps.
Resource Caps
The drawlist builder enforces hard caps on all resource pools. If any cap is exceeded, the builder records a sticky error and all subsequent commands become no-ops.Builder Caps
| Cap | Default | Description |
|---|---|---|
maxDrawlistBytes | 2 MiB (2,097,152) | Maximum total ZRDL buffer size |
maxCmdCount | 100,000 | Maximum number of commands per drawlist |
maxBlobBytes | 512 KiB (524,288) | Maximum total blob byte pool size |
maxBlobs | 10,000 | Maximum number of blob entries |
maxStringBytes | 512 KiB (524,288) | Maximum total string byte pool size |
maxStrings | 10,000 | Maximum number of interned strings |
Engine-Side Caps
The engine enforces its own independent limits when processing a submitted drawlist:| Engine cap | Description |
|---|---|
dl_max_total_bytes | Maximum accepted drawlist buffer size |
dl_max_cmds | Maximum command count |
dl_max_strings | Maximum string table entries |
dl_max_blobs | Maximum blob table entries |
dl_max_clip_depth | Maximum nesting depth of PUSH_CLIP/POP_CLIP |
dl_max_text_run_segments | Maximum segments in a single text run blob |
Structured Error Returns
DrawlistBuildResult
The builder’sbuild() method returns a discriminated union, never throws:
| Code | Meaning |
|---|---|
ZRDL_TOO_LARGE | Output exceeds a configured cap |
ZRDL_BAD_PARAMS | Invalid parameters passed to a drawing command |
ZRDL_FORMAT | Internal format constraint violated (alignment, size mismatch) |
ZRDL_INTERNAL | Implementation bug; should never occur in normal operation |
ZrResult Enum
Engine FFI functions returnZrResult (an int32):
ZrResult after every engine call and converts failures to ZrUiError instances.
ZrUiError Class
Runtime violations in the UI layer produceZrUiError:
| Code | Meaning |
|---|---|
ZRUI_INVALID_STATE | Operation called in wrong lifecycle phase |
ZRUI_MODE_CONFLICT | Incompatible render mode combination |
ZRUI_NO_RENDER_MODE | Render attempted without a configured mode |
ZRUI_REENTRANT_CALL | Recursive call into render/update |
ZRUI_UPDATE_DURING_RENDER | State mutation during render phase |
ZRUI_DUPLICATE_KEY | Two siblings share the same key |
ZRUI_DUPLICATE_ID | Two widgets share the same ID |
ZRUI_INVALID_PROPS | Configuration/props validation failure |
ZRUI_PROTOCOL_ERROR | Binary protocol violation from engine |
ZRUI_DRAWLIST_BUILD_ERROR | Builder returned an error result |
ZRUI_BACKEND_ERROR | Backend operation failed |
ZRUI_USER_CODE_THROW | User-provided view/event handler threw |
Sticky Error Semantics
The drawlist builder uses a sticky error pattern:- The first error encountered during command emission is recorded in the builder’s internal
errorfield. - All subsequent command calls (
clear(),drawText(), etc.) check for a sticky error and return immediately if one exists. They become no-ops. build()returns the sticky error as{ ok: false, error }.reset()clears the sticky error, allowing the builder to be reused for the next frame.
build() time:
build() and reset() should be called. Do not attempt to “recover” by continuing to emit commands—they will all be silently dropped.
Widget ID Validation
Interactive widgets require unique IDs:- Commit phase checks for duplicate IDs in the tree
- Fatal error
ZRUI_DUPLICATE_IDif duplicates found - Widget kinds that require IDs: button, input, checkbox, select, etc.
packages/core/src/runtime/commit.ts
Key Uniqueness Validation
Explicit keys must be unique among siblings:- Reconciliation checks for duplicate keys
- Fatal error
ZRUI_DUPLICATE_KEYif duplicates found
packages/core/src/runtime/reconcile.ts
Depth Limits
Rezi enforces depth limits to prevent stack overflow:- Layout engine tracks depth during traversal
- Error
ZRUI_INVALID_PROPS(detail:ZRUI_MAX_DEPTH) if exceeded
Input Sanitization
User-provided strings are sanitized: Control characters: Stripped from text content (except\n for newlines)
Invalid UTF-8: Replaced with U+FFFD (replacement character)
Zero-width characters: Preserved (used for combining characters)
Location: packages/core/src/layout/textMeasure.ts
Memory Safety
No manual memory management in TypeScript. Garbage collector handles cleanup. Native engine:- Pre-allocates buffers at creation time
- No dynamic allocation during frame rendering
- Explicit cleanup on
engine_destroy()
- Unmounted widgets have state cleaned up
- Event listeners auto-removed on unmount
- Timers cancelled on unmount
Security Considerations
Terminal injection: Rezi never emits raw ANSI sequences from user content. All rendering goes through validated ZRDL commands. Path traversal: Not applicable (no file I/O in core) Code injection: Not applicable (noeval() or dynamic code execution)
DoS via large trees: Mitigated by depth limits and resource caps
DoS via large strings: Mitigated by string byte caps
Related Documentation
- Protocol Overview — Binary protocol principles
- ZRDL Protocol — Drawlist validation rules
- Safety Rules (Protocol) — Protocol-specific safety patterns