Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/js/LayoutManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -829,10 +829,11 @@ lm.utils.copy( lm.LayoutManager.prototype, {
* @returns {void}
*/
_adjustToWindowMode: function() {
var popInButton = $( '<div class="lm_popin" title="' + this.config.labels.popin + '">' +
var popInButton = $( '<div class="lm_popin">' +
'<div class="lm_icon"></div>' +
'<div class="lm_bg"></div>' +
'</div>' );
popInButton.attr( 'title', this.config.labels.popin );

popInButton.click( lm.utils.fnBind( function() {
this.emit( 'popIn' );
Expand Down
2 changes: 1 addition & 1 deletion src/js/controls/HeaderButton.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
lm.controls.HeaderButton = function( header, label, cssClass, action ) {
this._header = header;
this.element = $( '<li class="' + cssClass + '" title="' + label + '"></li>' );
this.element = $( '<li></li>' ).attr( { 'class': cssClass, title: label } );
this._header.on( 'destroy', this._$destroy, this );
this._action = action;
this.element.on( 'click touchstart', this._action );
Expand Down
40 changes: 40 additions & 0 deletions test/header-button-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
describe( 'HeaderButton does not allow HTML injection via its label or cssClass', function(){
var lm = window.GoldenLayout.__lm;

var makeFakeHeader = function() {
return {
controlsContainer: $( '<ul></ul>' ),
on: function(){}
};
};

it( 'stores a malicious label as a literal title attribute without injecting markup', function(){
var header = makeFakeHeader(),
payload = '"><img src=x onerror=alert(1)>',
button = new lm.controls.HeaderButton( header, payload, 'lm_close', function(){} );

// The payload is stored verbatim in the title attribute...
expect( button.element.attr( 'title' ) ).toBe( payload );
// ...and crucially no extra (e.g. injected <img>) elements were created.
expect( header.controlsContainer.find( 'img' ).length ).toBe( 0 );
expect( header.controlsContainer.children().length ).toBe( 1 );
});

it( 'stores a malicious cssClass as a literal class attribute without injecting markup', function(){
var header = makeFakeHeader(),
payload = '"><img src=x onerror=alert(1)>',
button = new lm.controls.HeaderButton( header, 'a label', payload, function(){} );

expect( button.element.attr( 'class' ) ).toBe( payload );
expect( header.controlsContainer.find( 'img' ).length ).toBe( 0 );
expect( header.controlsContainer.children().length ).toBe( 1 );
});

it( 'applies a normal label and cssClass correctly', function(){
var header = makeFakeHeader(),
button = new lm.controls.HeaderButton( header, 'Close', 'lm_close', function(){} );

expect( button.element.attr( 'title' ) ).toBe( 'Close' );
expect( button.element.attr( 'class' ) ).toBe( 'lm_close' );
});
} );
45 changes: 45 additions & 0 deletions test/popin-button-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
describe( 'the pop-in button does not allow HTML injection via its label', function(){
var lm = window.GoldenLayout.__lm;

/**
* _adjustToWindowMode() is a private method that rebuilds document.body, so we
* call it against a stub `this` and snapshot/restore the document around it
* rather than spinning up a real sub-window (which Karma cannot host - see
* popout-tests.js).
*/
var runAdjustToWindowMode = function( popinLabel ) {
var stub = {
config: {
labels: { popin: popinLabel },
content: [ { title: 'a title' } ]
},
emit: function(){}
},
savedBody = $( 'body' ).children().detach(),
savedTitle = document.title;

try {
lm.LayoutManager.prototype._adjustToWindowMode.call( stub );
return stub.container.find( '.lm_popin' );
} finally {
$( 'body' ).html( '' ).append( savedBody );
document.title = savedTitle;
delete window.__glInstance;
}
};

it( 'stores a malicious label as a literal title attribute without injecting markup', function(){
var payload = '"><img src=x onerror=alert(1)>',
popInButton = runAdjustToWindowMode( payload );

expect( popInButton.length ).toBe( 1 );
expect( popInButton.attr( 'title' ) ).toBe( payload );
expect( popInButton.find( 'img' ).length ).toBe( 0 );
});

it( 'applies a normal label correctly', function(){
var popInButton = runAdjustToWindowMode( 'pop in' );

expect( popInButton.attr( 'title' ) ).toBe( 'pop in' );
});
} );
Loading