Files
twenty/packages/twenty-front-component-renderer/src/host/utils/__tests__/parseCssString.test.ts
T
Raphaël Bosi 49095dcfe0 Drop unsafe props from front component elements (#22458)
## What

Front components are third-party React components rendered into the host
page through a restricted element allow-list. `filterProps` (where their
props become real DOM attributes) used to forward unrecognized values
as-is, which left two ways to run script in the host origin:

- an `on*` attribute with a string value, which React renders as an
inline event handler;
- a dangerous-scheme URL (`javascript:`, `data:`, `vbscript:`) on a
link, which executes on navigation.

## Change

`filterProps` now drops both:

- `on*` props are kept only when the value is a real function (still
wrapped as before); any non-function `on*` is dropped.
- `javascript:` / `data:` / `vbscript:` URLs are dropped, but only on
**navigation targets** (`<a>`/`<area>` `href`/`xlink:href`, `<form>`
`action`, `<button>`/`<input>` `formaction`), after normalizing away
control-character obfuscation (e.g. `java\tscript:`). Resource-loading
attributes are left alone, so `<img src="data:image/...">` keeps
working.

Well-behaved components are unaffected: function handlers are still
wrapped and normal URLs pass through. Host-side only, no worker or SDK
changes.

## Scope: the actual behavior change is small

The diff looks large, but most of it is **not** a behavior change.
`createHtmlHostWrapper.ts` (~460 lines) was split into
one-export-per-file utils (`filterProps`, `serializeEvent`,
`parseCssString`, `hasDangerousUrlScheme`, etc.), each with its own unit
test, leaving `createHtmlHostWrapper.ts` as a thin orchestrator. Those
helpers were **moved unchanged** — the only real logic change is the
`filterProps` hardening described above. The pre-existing render-based
integration test passes untouched, which confirms the split is
behavior-neutral; the rest of the new files are extractions plus added
test coverage.

## Why these schemes, and only on navigation targets

Per MDN, `javascript:` (and `data:`) URLs are dangerous specifically
where a URL is a *navigation target*, not where it is a *resource
location* (like an image `src`) — which is exactly how the check is
scoped:

- [`javascript:` URLs
(MDN)](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/javascript)
- [`data:` URLs
(MDN)](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data)
- [URI schemes overview
(MDN)](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes)

This is a prerequisite for later work that widens the raw-attribute
surface (innerHTML rendering).
2026-07-02 14:40:15 +00:00

36 lines
1.1 KiB
TypeScript

import { parseCssString } from '../parseCssString';
describe('parseCssString', () => {
it('should return the input unchanged when it is not a non-empty string', () => {
expect(parseCssString(undefined)).toBeUndefined();
expect(parseCssString('')).toBe('');
});
it('should convert kebab-case properties to camelCase', () => {
expect(parseCssString('background-color: red')).toEqual({
backgroundColor: 'red',
});
});
it('should keep custom properties as-is', () => {
expect(parseCssString('--my-var: 1px')).toEqual({ '--my-var': '1px' });
});
it('should parse multiple declarations and tolerate a trailing semicolon', () => {
expect(parseCssString('color: red; font-size: 12px;')).toEqual({
color: 'red',
fontSize: '12px',
});
});
it('should skip declarations without a colon', () => {
expect(parseCssString('color: red; invalid')).toEqual({ color: 'red' });
});
it('should only split on the first colon so values may contain colons', () => {
expect(parseCssString('background: url(http://example.com)')).toEqual({
background: 'url(http://example.com)',
});
});
});