mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix(packages): escape HTML special chars in serializeAttributes to prevent XSS (#1670)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Wesley Luyten <me@wesleyluyten.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
Wesley Luyten
parent
9170a5879e
commit
accf4bfa34
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { namedNodeMapToObject, serializeAttributes } from '../attributes';
|
||||
|
||||
describe('serializeAttributes', () => {
|
||||
it('serializes a boolean (empty-string) attribute without a value', () => {
|
||||
expect(serializeAttributes({ muted: '' })).toBe(' muted');
|
||||
});
|
||||
|
||||
it('serializes a normal value unchanged when no special characters are present', () => {
|
||||
expect(serializeAttributes({ preload: 'metadata' })).toBe(' preload="metadata"');
|
||||
});
|
||||
|
||||
it('serializes multiple attributes', () => {
|
||||
expect(serializeAttributes({ autoplay: '', preload: 'metadata' })).toBe(' autoplay preload="metadata"');
|
||||
});
|
||||
|
||||
it('escapes double quotes in attribute values', () => {
|
||||
expect(serializeAttributes({ src: '" onerror="alert(1)' })).toBe(' src="" onerror="alert(1)"');
|
||||
});
|
||||
|
||||
it('escapes angle brackets in attribute values', () => {
|
||||
expect(serializeAttributes({ src: '"><script>bad</script><video x="' })).toBe(
|
||||
' src=""><script>bad</script><video x=""'
|
||||
);
|
||||
});
|
||||
|
||||
it('escapes ampersands in attribute values', () => {
|
||||
expect(serializeAttributes({ src: 'a&b' })).toBe(' src="a&b"');
|
||||
});
|
||||
|
||||
it('escapes ampersand before other entities to prevent double-encoding', () => {
|
||||
// If '&' were escaped after '"', the existing '"' would become '&quot;'
|
||||
// which the browser would decode as the literal text '"' instead of '"'.
|
||||
// This test ensures the pre-existing entity reference is preserved correctly.
|
||||
expect(serializeAttributes({ src: 'x"y' })).toBe(' src="x&quot;y"');
|
||||
});
|
||||
|
||||
it('escapes all four special characters together', () => {
|
||||
const value = '&"<>';
|
||||
expect(serializeAttributes({ src: value })).toBe(' src="&"<>"');
|
||||
});
|
||||
|
||||
it('returns an empty string for an empty object', () => {
|
||||
expect(serializeAttributes({})).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('namedNodeMapToObject', () => {
|
||||
it('copies raw attribute values without escaping', () => {
|
||||
const el = document.createElement('div');
|
||||
el.setAttribute('src', '"raw"');
|
||||
el.setAttribute('muted', '');
|
||||
|
||||
const result = namedNodeMapToObject(el.attributes);
|
||||
|
||||
expect(result.src).toBe('"raw"');
|
||||
expect(result.muted).toBe('');
|
||||
});
|
||||
|
||||
it('returns an empty object when there are no attributes', () => {
|
||||
const el = document.createElement('div');
|
||||
|
||||
expect(namedNodeMapToObject(el.attributes)).toEqual({});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user