Skip to content

harden proto storage — security, correctness, performance, tests - #89

Merged
kacy merged 6 commits into
mainfrom
harden/proto-storage
Feb 11, 2026
Merged

harden proto storage — security, correctness, performance, tests#89
kacy merged 6 commits into
mainfrom
harden/proto-storage

Conversation

@kacy

@kacy kacy commented Feb 11, 2026

Copy link
Copy Markdown
Owner

summary

  • adds descriptor size limit (10MB) and field path depth limit (16) to prevent DoS through PROTO.REGISTER and field operations
  • adds O(1) message descriptor cache, replacing O(n) linear scan across all registered schemas on every get_field/set_field/validate call
  • optimizes single-segment field reads to skip cloning the entire DynamicMessage
  • fixes SETFIELD and DELFIELD silently resetting key TTL to no-expiry — remaining TTL is now preserved through the read-modify-write cycle
  • adds unit tests for nested set/clear, auto-init of intermediate messages, u64 overflow edge cases, and path validation
  • adds integration tests for TTL preservation, nested paths, and duplicate registration

what was tested

  • cargo test -p emberkv-core --features protobuf --lib — 332 tests pass (31 schema-specific)
  • cargo test -p ember-protocol — 1 test passes
  • cargo test -p ember-integration-tests — 109 tests pass (40 proto-specific)
  • cargo clippy --workspace --features protobuf -- -D warnings — clean
  • cargo check without protobuf feature — no regressions

design considerations

  • TTL preservation returns remaining TTL from proto_get as a Duration and threads it through to the write-back ProtoSet. this adds ~8 bytes to the ProtoValue enum variant but avoids a second shard roundtrip to fetch TTL separately.
  • the message cache is a flat HashMap<String, MessageDescriptor> on SchemaRegistry, populated during register() and restore(). the pool field on RegisteredSchema is kept for test helpers that build dynamic messages directly.
  • concurrent-mode TTL tests are skipped because proto commands route through engine shards while TTL checks the concurrent keyspace — these are fundamentally different stores.

kacy added 6 commits February 10, 2026 20:49
adds two security checks to prevent DoS through the proto API:
- MAX_DESCRIPTOR_BYTES (10MB) rejects oversized descriptors before
  decoding in PROTO.REGISTER
- MAX_FIELD_PATH_DEPTH (16) rejects deeply nested field paths in
  get_field, set_field, and clear_field

includes unit tests for both limits plus edge cases (double-dot
and trailing-dot paths).
replaces O(n) linear scan across all registered schemas with a
HashMap<String, MessageDescriptor> that's populated during register()
and restore(). common path (every get_field/set_field/validate call)
now does a single hash lookup instead of iterating all pools.
adds an early return for the common case of reading a top-level field
(e.g. "name") that borrows directly from the decoded message instead
of cloning the entire DynamicMessage. mirrors the pattern already
used in resolve_field_path_mut.
covers edge cases not previously tested:
- set_field on nested paths (inner.value)
- clear_field on nested paths
- auto-initialization of intermediate messages on nested set
- u64::MAX returns bulk string (too large for i64)
- u64 that fits in i64 returns integer frame
proto_get now returns remaining TTL alongside type_name and data.
the SETFIELD/DELFIELD handlers in both sharded and concurrent modes
pass the existing TTL through to the write-back ProtoSet, so a key's
expiry is no longer silently reset to no-expiry on field mutation.
adds sharded-mode tests verifying that SETFIELD and DELFIELD preserve
the key's TTL instead of resetting it. also covers nested field paths
(inner.value) for both set and del, and duplicate schema registration
rejection.

concurrent-mode tests cover nested paths but skip TTL verification
since proto values route through engine shards while TTL checks the
concurrent keyspace.
@kacy
kacy merged commit f0ef62d into main Feb 11, 2026
5 of 7 checks passed
@kacy
kacy deleted the harden/proto-storage branch February 11, 2026 02:02
kacy added a commit that referenced this pull request Feb 11, 2026
* fix: add descriptor size and field path depth limits

adds two security checks to prevent DoS through the proto API:
- MAX_DESCRIPTOR_BYTES (10MB) rejects oversized descriptors before
  decoding in PROTO.REGISTER
- MAX_FIELD_PATH_DEPTH (16) rejects deeply nested field paths in
  get_field, set_field, and clear_field

includes unit tests for both limits plus edge cases (double-dot
and trailing-dot paths).

* perf: add message descriptor cache for O(1) find_message

replaces O(n) linear scan across all registered schemas with a
HashMap<String, MessageDescriptor> that's populated during register()
and restore(). common path (every get_field/set_field/validate call)
now does a single hash lookup instead of iterating all pools.

* perf: optimize resolve_field_path for single-segment reads

adds an early return for the common case of reading a top-level field
(e.g. "name") that borrows directly from the decoded message instead
of cloning the entire DynamicMessage. mirrors the pattern already
used in resolve_field_path_mut.

* test: add nested path and u64 overflow unit tests

covers edge cases not previously tested:
- set_field on nested paths (inner.value)
- clear_field on nested paths
- auto-initialization of intermediate messages on nested set
- u64::MAX returns bulk string (too large for i64)
- u64 that fits in i64 returns integer frame

* fix: preserve TTL across SETFIELD and DELFIELD operations

proto_get now returns remaining TTL alongside type_name and data.
the SETFIELD/DELFIELD handlers in both sharded and concurrent modes
pass the existing TTL through to the write-back ProtoSet, so a key's
expiry is no longer silently reset to no-expiry on field mutation.

* test: add integration tests for TTL preservation and nested paths

adds sharded-mode tests verifying that SETFIELD and DELFIELD preserve
the key's TTL instead of resetting it. also covers nested field paths
(inner.value) for both set and del, and duplicate schema registration
rejection.

concurrent-mode tests cover nested paths but skip TTL verification
since proto values route through engine shards while TTL checks the
concurrent keyspace.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant