a2ui-project / a2ui-project/a2ui
web_core: DataModel accepts unbounded list indices, amplifying the serialized client data model
- Vorherrschende Sprache
- TypeScript
- Sterne
- 16.4k
- Forks
- 1.3k
- Ø Merge
- 2 T. 13 Std.
- Gemergte PRs (30 T.)
- 134
Beschreibung
`web_core`'s `DataModel` accepts an arbitrarily large list index, such as `/items/999999999`. The write itself is cheap, because JavaScript arrays are sparse. The cost appears later, when that array is turned into JSON.
## Measured
Against the built `web_core` `DataModel`:
```
set('/items/1000000', 'x')
heap growth: ~14 KB
array length: 1000001
serialized length: 5,000,002 chars
```
`JSON.stringify` expands every hole to `null`. At index 10⁹ that is roughly 5 GB of JSON from a 14 KB write.
## Why it matters
The index does not have to come from the page's own code. It can come from the agent:
1. The agent creates a surface with `sendDataModel: true`. That opts the surface into sending its data model along with anything the client sends back, so the agent can see form state.
2. The agent sends `updateDataModel` with `path: "/items/999999999"`. It looks ordinary and `web_core` accepts it. Cost so far: ~14 KB.
3. Later the user does something that the client reports, for example tapping a `Button` with an `action`. Before sending, the client gathers its data model via [`MessageProcessor.getClientDataModel()`](https://github.com/a2ui-project/a2ui/blob/57491139217943f57e5e0debec75ff5dbe0485de/renderers/web_core/src/v0_9/processing/message-processor.ts#L238), which reads `dataModel.get('/')` for every surface with `sendDataModel` enabled.
4. The transport JSON-encodes that object into the [`a2uiClientDataModel`](https://github.com/a2ui-project/a2ui/blob/57491139217943f57e5e0debec75ff5dbe0485de/specification/v0_9_1/json/client_data_model.json) metadata field of the outgoing message.
5. The array reports `length: 1000000000`, so `JSON.stringify` writes `null` a billion times. The outgoing message is roughly 5 GB.
So one small, well-formed message from the agent makes the client build an enormous payload on its next turn. The client pays the cost, and the agent controls the index.
## Why the two implementations differ
- **Dart** lists are dense, so writing index N allocates N+1 slots. It already caps this: [`maxAutoVivifyIndex = 10000`](https://github.com/a2ui-project/a2ui/blob/57491139217943f57e5e0debec75ff5dbe0485de/dart/a2ui_core/lib/src/core/data_model.dart#L23), enforced in `DataModel.set` at [L86](https://github.com/a2ui-project/a2ui/blob/57491139217943f57e5e0debec75ff5dbe0485de/dart/a2ui_core/lib/src/core/data_model.dart#L86) and [L125](https://github.com/a2ui-project/a2ui/blob/57491139217943f57e5e0debec75ff5dbe0485de/dart/a2ui_core/lib/src/core/data_model.dart#L125).
- **web_core** arrays are sparse, so the same write costs nothing on the heap and [`DataModel.set`](https://github.com/a2ui-project/a2ui/blob/57491139217943f57e5e0debec75ff5dbe0485de/renderers/web_core/src/v0_9/state/data-model.ts#L105) has no cap.
Sparse arrays protect the heap, not the payload. Both implementations need a bound; they just need it for different resources.
## Proposal
1. Agree a shared upper bound on auto-vivified list indices. Dart's `10000` is arbitrary and worth choosing deliberately.
2. Enforce it in `web_core`'s `DataModel.set`, throwing `A2uiDataError` as it already does for other invalid segments.
3. Promote the behaviour to [`conformance/core/data_model.yaml`](https://github.com/a2ui-project/a2ui/blob/57491139217943f57e5e0debec75ff5dbe0485de/conformance/core/data_model.yaml) and drop the package-local tests.
4. Check the other renderers and SDKs for the same gap.
Step 2 changes what input a shipped renderer accepts, so it needs a maintainer decision rather than being folded into a test change.
## Repro
```js
import {DataModel} from './dist/src/v0_9/state/data-model.js';
const m = new DataModel({items: ['a', 'b', 'c']});
m.set('/items/1000000', 'x');
console.log(m.get('/items').length); // 1000001
console.log(JSON.stringify(m.get('/items')).length); // 5000002
```
## Context
Surfaced while migrating `data-model.test.ts` into the shared conformance suite in #2408. The exclusion is currently documented in the [suite header](https://github.com/a2ui-project/a2ui/blob/57491139217943f57e5e0debec75ff5dbe0485de/conformance/core/data_model.yaml) and in the `web_core` test's doc comment; both should be removed once this is resolved.
Beitragsleitfaden
Bewertung
Dieses Issue wurde noch nicht bewertet.