Security & Guardrails
Relevant source files
The following files were used as context for generating this wiki page:
Introduction
Security and guardrails in @b2m9/keyfold focus on ensuring data integrity and preventing common JavaScript vulnerabilities during the process of folding partial deltas into state trees. The library is designed to be immutable and failure-safe, meaning that a merge operation either completes successfully without mutating the original base state or throws an error without leaving partial writes or a corrupted state behind.
The primary scope of these guardrails includes preventing prototype pollution, enforcing strict identity management in keyed lists, and validating configuration paths before execution. By adhering to these principles, the system ensures deterministic and idempotent results, allowing stores to drop duplicate frames with a simple equality check.
Sources: README.md:143-157, AGENTS.md:21-23
Immutability and Failure Safety
The core architectural principle of keyfold is that the base state is never mutated. All modifications occur on working copies, and the final result is only returned if the entire operation succeeds. If an error occurs—such as a duplicate identity or a malformed tombstone—a KeyfoldMergeError is thrown, and the original tree remains untouched.
Container Folding Logic
When folding objects or keyed lists, keyfold uses "synthetic containers" (empty objects or arrays) if the base data has the wrong shape. This allows the fold to continue while maintaining structural sharing: if a delta results in no actual changes, the library returns the base reference directly.
The diagram shows the decision flow for maintaining immutability and structural sharing during a fold operation.
Sources: src/merge.ts:39-65, src/merge.ts:133-140, AGENTS.md:21-25
Prototype Pollution Prevention
To prevent malicious deltas from compromising the runtime environment, keyfold explicitly ignores unsafe keys during the recursive object fold. This prevents "Prototype Pollution" attacks where a delta might attempt to overwrite the global Object.prototype.
Unsafe Keys
The library maintains a constant set of UNSAFE_KEYS that are filtered out during the enumeration of delta keys:
__proto__constructorprototype
Additionally, the utility function isPlainObject ensures that recursion only enters objects with a null prototype or those inheriting directly from Object.prototype. When copying objects, the library uses Object.create(Object.getPrototypeOf(source)) to preserve the original prototype (e.g., for null prototype objects) without allowing delta-injected properties to modify them.
Sources: src/objects.ts:2-15, src/merge.ts:93-95
Keyed List Guardrails
Keyed list reconciliation is the most complex part of the library and carries specific security requirements regarding identities and tombstones.
Identity Validation
Identities must be unique, stable strings or non-NaN numbers. The system enforces the following constraints during the reconciliation process:
- No Coercion:
1and"1"are treated as distinct identities. - Duplicate Detection: The library throws if it detects duplicate identities in either the
baselist or thedeltalist. - Reserved Fields: The field
$deleteis reserved for tombstones and cannot be a part of the base data. - Ambiguity Prevention: If
wireDeletesis enabled, theDELETE_TOKENcannot be used as an identity value.
Tombstone Safety
A tombstone is an object containing only the identity field and $delete: true. The library strictly validates tombstones to ensure they do not contain other patch fields, which would create ambiguity.
| Constraint | Enforcement Mechanism | Error Type |
|---|---|---|
| Unique IDs | Scans list and uses a Map to index delta identities. | KeyfoldMergeError |
| Valid ID Types | Checks typeof id for string or number (non-NaN). | KeyfoldMergeError |
| Clean Tombstones | Ensures $delete is exactly true and no extra keys exist. | KeyfoldMergeError |
| Reserved Tokens | Rejects DELETE_TOKEN as ID if wireDeletes is active. | KeyfoldMergeError |
Sources: src/reconcile.ts:24-42, src/reconcile.ts:91-137, README.md:89-95
Path Policy & Configuration Guardrails
Configuration is validated once when the merger is created via createMerger. This prevents runtime overhead and ensures that contradictory policies are caught early.
Reachability and Grammar
The compileOptions function parses path strings (dot-separated properties with optional [] suffixes) and builds a PolicyNode tree. It enforces several "dead config" rules:
- Replace Dominance: If a path is marked for
replace, nokeyByor furtherreplacepolicies can exist deeper in that subtree. - Keyed Segment Requirements: A segment using
[](e.g.,items[]) is only allowed if the parent path (items) has an associatedkeyByidentity field. - Reserved Property Names: Paths cannot step through reserved properties like
constructor.
The configuration lifecycle ensures that the merger is only created if the path policies are logically consistent.
Sources: src/paths.ts:31-75, src/paths.ts:121-140, AGENTS.md:38-40
Wire Protocol Security
When wireDeletes is enabled, the library interprets the DELETE_TOKEN (@@keyfold/delete) as a field deletion. This is intended for JSON transports where symbols cannot be sent.
- Scoped Interpretation: The token is only interpreted at interpreted object-field positions. It remains a literal string inside
replaceboundaries, unkeyed arrays, or as part of a new item insertion. - Static Type Limits: The
Delta<T>type prevents theDELETEsymbol from being used on required fields, but it cannot statically block theDELETE_TOKENstring. Users are responsible for validating wire input before it reaches the merger.
Sources: src/sentinels.ts:5-6, README.md:126-134, AGENTS.md:31-36
Summary
Security & Guardrails in keyfold provide a robust environment for state reconciliation by prioritizing immutability, preventing prototype manipulation, and enforcing strict data contracts for keyed lists. By validating configurations at creation and ensuring that all merge operations are atomic and failure-safe, the library prevents common state corruption issues and security vulnerabilities in reactive applications.
Sources: README.md:143-157, AGENTS.md:6-18