a2ui-project / a2ui-project/a2ui
web_core: DataModel accepts unbounded list indices, amplifying the serialized client data model
- Ngôn ngữ chính
- TypeScript
- Star
- 16.4k
- Fork
- 1.3k
- Merge trung bình
- 2 ngày 13 giờ
- Pull request đã merge (30 ngày)
- 134
Mô tả
`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.
Hướng dẫn đóng góp
Hướng nghiên cứu
Examine the DataModel.set method in renderers/web_core/src/v0_9/state/data-model.ts to understand the current sparse array handling. Compare with the Dart implementation's maxAutoVivifyIndex in dart/a2ui_core/lib/src/core/data_model.dart. The fix involves adding a similar bound in the TypeScript code, throwing an A2uiDataError for invalid indices. Test the change using the provided repro script and ensure it aligns with the conformance spec in conformance/core/data_model.yaml.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- javascript, typescript
- Lĩnh vực
- backend-api-design, data
- Loại issue
- Lỗi
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 65/100