a2ui-project / a2ui-project/a2ui
web_core: DataModel accepts unbounded list indices, amplifying the serialized client data model
- 主要语言
- TypeScript
- 星标
- 16.4k
- 派生
- 1.3k
- 平均合并
- 3 天 15 小时
- 30 天内合并 PR
- 134
描述
`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.
贡献指南
调研方向
检查 renderers/web_core/src/v0_9/state/data-model.ts 中的 DataModel.set 方法,以了解当前对稀疏数组的处理方式。将其与 dart/a2ui_core/lib/src/core/data_model.dart 中 Dart 实现的 maxAutoVivifyIndex 进行比较。修复涉及在 TypeScript 代码中添加类似的边界,并对无效索引抛出 A2uiDataError。使用提供的复现脚本测试更改,并确保其符合 conformance/core/data_model.yaml 中的 conformance 规范。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- javascript, typescript
- 领域
- backend-api-design, data
- Issue 类型
- 缺陷
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 活跃度
- 活跃
- 描述清晰度
- 描述清楚
- 新手友好度
- 65/100