a2ui-project / a2ui-project/a2ui

web_core: DataModel accepts unbounded list indices, amplifying the serialized client data model

Abierto
#2,420 2 comentarios 1 reacción 0 asignados Ver en GitHub
P2 type: bug
Lenguaje dominante
TypeScript
Estrellas
16.4k
Forks
1.3k
Merge medio
2 d 13 h
PR fusionados (30 d)
134

Descripción

`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.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.