Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix(input): prevent browsers from autofilling hidden inputs
Browse files Browse the repository at this point in the history
Autofilling with previous values (which will then be `$interpolate`ed) could lead to XSS or errors
  • Loading branch information
jbedard authored and petebacondarwin committed Dec 2, 2018
1 parent a8bfeff commit 7cbb104
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 2 deletions.
5 changes: 3 additions & 2 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
htmlAnchorDirective,
inputDirective,
inputDirective,
hiddenInputBrowserCacheDirective,
formDirective,
scriptDirective,
selectDirective,
Expand Down Expand Up @@ -221,7 +221,8 @@ function publishExternalAPI(angular) {
ngModelOptions: ngModelOptionsDirective
}).
directive({
ngInclude: ngIncludeFillContentDirective
ngInclude: ngIncludeFillContentDirective,
input: hiddenInputBrowserCacheDirective
}).
directive(ngAttributeAliasDirectives).
directive(ngEventDirectives);
Expand Down
42 changes: 42 additions & 0 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -2193,6 +2193,48 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
}];


var hiddenInputBrowserCacheDirective = function() {
var valueProperty = {
configurable: true,
enumerable: false,
get: function() {
return this.getAttribute('value') || '';
},
set: function(val) {
this.setAttribute('value', val);
}
};

return {
restrict: 'E',
priority: 200,
compile: function(_, attr) {
if (lowercase(attr.type) !== 'hidden') {
return;
}

return {
pre: function(scope, element, attr, ctrls) {
var node = element[0];

// Support: Edge
// Moving the DOM around prevents autofillling
if (node.parentNode) {
node.parentNode.insertBefore(node, node.nextSibling);
}

// Support: FF, IE
// Avoiding direct assignment to .value prevents autofillling
if (Object.defineProperty) {
Object.defineProperty(node, 'value', valueProperty);
}
}
};
}
};
};



var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
/**
Expand Down
13 changes: 13 additions & 0 deletions test/e2e/fixtures/back2dom/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html ng-app="test">
<body ng-class="{hacked: internalFnCalled}">
<form>
<input id="input1" type="hidden" value="{{value}}" />
<input id="input2" type="hidden" ng-value="value" />

<textarea ng-model="value"></textarea>
</form>
<script src="angular.js"></script>
<script src="script.js"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions test/e2e/fixtures/back2dom/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

angular
.module('test', [])
.run(function($rootScope) {
$rootScope.internalFnCalled = false;

$rootScope.internalFn = function() {
$rootScope.internalFnCalled = true;
};
});
68 changes: 68 additions & 0 deletions test/e2e/tests/input-hidden.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,72 @@ describe('hidden thingy', function() {
var expectedValue = browser.params.browser === 'safari' ? '{{ 7 * 6 }}' : '';
expect(element(by.css('input')).getAttribute('value')).toEqual(expectedValue);
});

it('should prevent browser autofill on browser.refresh', function() {

loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');

element(by.css('textarea')).sendKeys('{{ internalFn() }}');

expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');

browser.refresh();
expect(element(by.css('body')).getAttribute('class')).toBe('');
});

it('should prevent browser autofill on location.reload', function() {

loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');

element(by.css('textarea')).sendKeys('{{ internalFn() }}');

expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');

browser.driver.executeScript('location.reload()');
expect(element(by.css('body')).getAttribute('class')).toBe('');
});

it('should prevent browser autofill on history.back', function() {

loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');

element(by.css('textarea')).sendKeys('{{ internalFn() }}');

expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');

loadFixture('sample');

browser.driver.executeScript('history.back()');
expect(element(by.css('body')).getAttribute('class')).toBe('');
});

it('should prevent browser autofill on history.forward', function() {

loadFixture('sample');
loadFixture('back2dom');
expect(element(by.css('#input1')).getAttribute('value')).toEqual('');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('');

element(by.css('textarea')).sendKeys('{{ internalFn() }}');

expect(element(by.css('#input1')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('#input2')).getAttribute('value')).toEqual('{{ internalFn() }}');
expect(element(by.css('body')).getAttribute('class')).toBe('');

browser.driver.executeScript('history.back()');
browser.driver.executeScript('history.forward()');
expect(element(by.css('body')).getAttribute('class')).toBe('');
});
});

0 comments on commit 7cbb104

Please sign in to comment.