-
Notifications
You must be signed in to change notification settings - Fork 27.5k
feat($parse): secure expressions by hiding "private" properties #4509
Conversation
Oh golly! Another PR from a favorite contributor. I'm giddy with joy! |
@IgorMinar: I have another idea for this that I want to run by you. I'm considering allowing access to "private" fields if they resolve to a non-function object. One could still use it to perform mischief depending on the app (e.g. if some controller published a function looked something like |
I am not convinced by this change. In the first place, the only real pattern that this allows is using scope to share private properties between controllers. Other aspects of private properties can be secured by placing them in the closure of the controller and exposing secure getters and setters on the scope. Using scope to share private data between controllers seems like a bad idea to me. This would be better done using services that are responsible for holding this shared state, which is injected into the closure of the controller when it is instantiated. Also, security issues like this are hard for most programmers to comprehend and I am not convinced that, even with this change in place, programmers would use it correctly to secure their code. Finally, it makes me feel even more strongly that the "controller as" syntax is not a great pattern. Controllers were originally an excellent place to put helper functions, aspects of which can be published, securely, to the scope. By allowing the template designer to attach the entire controller object to the scope, you basically open up this security issue. |
@@ -726,7 +739,7 @@ Parser.prototype = { | |||
return v; | |||
}, { | |||
assign: function(self, value, locals) { | |||
var key = indexFn(self, locals); | |||
var key = ensureSafeMemberName(indexFn(self, locals), parser.text); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't I set "constructor", when I can get it?
Discussed with @chirayuk in person. LGTM, just do the little style fixes. |
throw $parseMinErr('isecfld', | ||
'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', fullExpression); | ||
} else if (name.charAt(0) === '_' || name.charAt(name.length-1) === '_') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: remove the else
@petebacondarwin This change does not make sharing code between controllers through scope inheritance (which we do not recommend) any simpler. I agree with you, that sharing stuff between controllers through scope inheritance is not a good thing, but I don't think this change promotes this style in any way. Please explain more, if you think it does... You can achieve "private" state for controllers even with "controller as" syntax: var MyCtrl = function() {
var privateMethod = function() {};
this.publicMethod = function() {};
}; I think this change is mostly for people using the "prototype" style (eg. Google internal projects). |
BREAKING CHANGE: This commit introduces the notion of "private" properties (properties whose names begin and/or end with an underscore) on the scope chain. These properties will not be available to Angular expressions (i.e. {{ }} interpolation in templates and strings passed to `$parse`) They are freely available to JavaScript code (as before). Motivation ---------- Angular expressions execute in a limited context. They do not have direct access to the global scope, Window, Document or the Function constructor. However, they have direct access to names/properties on the scope chain. It has been a long standing best practice to keep sensitive APIs outside of the scope chain (in a closure or your controller.) That's easier said that done for two reasons: (1) JavaScript does not have a notion of private properties so if you need someone on the scope chain for JavaScript use, you also expose it to Angular expressions, and (2) the new "controller as" syntax that's now in increased usage exposes the entire controller on the scope chain greatly increaing the exposed surface. Though Angular expressions are written and controlled by the developer, they (1) typically deal with user input and (2) don't get the kind of test coverage that JavaScript code would. This commit provides a way, via a naming convention, to allow publishing/restricting properties from controllers/scopes to Angular expressions enabling one to only expose those properties that are actually needed by the expressions.
You just broke the apps of everyone who is building with CouchDB. The convention there is that document ID's and revisions are prefixed with underscores. |
@krotscheck this only "breaks" them in the sense that you cannot directly bind to these properties. You can easily:
Developers should have to explicitly choose to expose these kinds of properties in their app. This is consistent with the intention of the change |
MongoDB also has primary keys stored on an _id field, iirc. |
@btford That may be the case, however javascript already provides the ability to make variables private via scope isolation. Why enforce this convention? Furthermore, why fix something that isn't broken? angular.module('mymodule').controller('myController', function() {
var privateVariable = 'this.variable.is.private';
return {
getPrivateVariable: function () { return privateVariable; }
}
}); Note, I'm using "Scope isolation" in the context of "Scope inside of the function/lambda", not "AngularJS scope" |
I try to imagine a use case where this actually safes us from anything? I can't see the real benefit. |
@krotscheck Agreed. I don't understand why you can just use javascript scopes to hide state like this. If this feature is needed, it shouldn't be done by detecting an underscore. It could be done by declaring that variable is private, perhaps a helper method. |
For a breaking change this is of very small benefit and probably even to a smaller number of users. I like this idea, but as a compromise: let us toggle this behavior. |
@btford Bindings are not the only ones impacted. More investigation suggests that the introduced bug exists in ngResource, where resource variable interpolation triggers ensureSafeMemberName(). return $resource(myBaseUri + '/resource/:_id', {_id: '@_id'}); |
This benefits a prototype-style controller using the "controller as" syntax: function WidgetCtrl() {
}
WidgetCtrl.prototype._doSomethingPrivate = function () {
};
WidgetCtrl.prototype.doSomethingPublic = function () {
// ...
};
angular.module('myApp').controller('WidgetCtrl', WidgetCtrl); <div ng-controller="WidgetCtrl as widget">
<button ng-click="widget.doSomethingPublic()">do something</button>
</div> where |
In a constructive conversation, I'd avoid making these types of assumptions. A feature that is of "small benefit" to you might be important to others. This makes security audits much easier for large applications using controllers with methods defined on their prototype. I understand that this is annoying issue, and the implications @krotscheck pointed out in
A flag that disables this feature seems reasonable. @chirayuk, what do you think? Would anyone like to prepare a PR? |
@btford just out of curiosity, why exactly does it make security audits easier? What exactly makes it more secure that such method can't be accessed from within expressions? Since we all know that true privacy is not possible when using a prototype based programming style in JavaScript we have long lived by the rule to just accept things as private when they were communicated as such. |
@btford or by "secure" do you mean "securing" that developers don't abuse the code base? I'm just curious if we mean the same thing with "security" in this context. |
I quote : Hi thanks for the opportunity to discuss that "feature".
Yet you want to do that ? I understand the point, but convention over configuration is a bad,thing.Want that ? let developpers configure filtered methods through an Angular API. _ for "private" members is not a good practice in any language. it's basically HUNGARIAN NOTATION and not understanding the nature of javascript; Some mark their private with private prefix, some other use @@ or other symbols so members can only be called when they are quoted ( myobject['@@mymember'] ) , Angular owns the $$ namespace . It will make things confusing if you go that route. Regards. |
@Mparaiso good point with |
@btford Fair enough. As @vojtajina commented, this is important to devs writing internal apps for Google. I'll look at writing a PR later tonight if nobody has beaten me to it. I'll hazard a guess the code would be similar to a simple toggle like |
I'd rather see it removed than being a flag, but if it's a flag, please make the default that underscores are allowed, as this was the 1.2.0-rc.3 behavior. On another note, this should have been added to one of the release candidates instead of being shoved into the actual release, so the full impact of the code could be assessed. Is it too late to call this rc4? |
@tortlieb agreed, I know it's not really my place to say but I feel bringing in breaking changes like this between rc3 and the actual release is crazy. Personally I'd like 1.2.0 to be a really solid version that works as expected and that is documented well. |
The I'm going to try to address some of the questions below.
The short answer is "because Angular now guarantees that It's hard to think of a simple, clear, obvious example off the top of my head, because most exploits are none of these things. function Ctrl () {}
Ctrl.prototype._deleteUser = function () {};
Ctrl.prototype.promptToDeleteUser = function () {
// bring up dialog box, do some sanity checks, then _delete
}; <div ng-controller="Ctrl as ctrl">
<select ng-model="action">
<option value="promptToDeleteUser"></option>
</select>
<button ng-click="ctrl[action]()">
</div> Consider in AngularJS pre-1.2.0: you'd think that because there is no If you use the prefix, it makes it more obvious to verify that your app is safe: you know that Why not just defensively program and do these checks in all methods? There are cases where for performance implications, it would be slow to always do a full set of checks/sanitization. This offers developers a pragmatic choice. Why not just use closures? This means you get a bound method for each instance of a controller. With prototypes, you only have one. This potentially saves memory on large apps, or for apps with more constraints (like ones targeting mobile). It's also easier to use prototypes with tools like Google Closure Compiler. We think developers should have the choice to use either in a way that is safe. You can never be completely certain with security, but offering developers this guarantee makes it easier to cull some of the possible attack vectors. This, in turn means that security reviewers can focus on the un-prefixed parts of the app that are exposed to users/attackers. I don't think that security alone is the only benefit from this change. I've switched to this style for a few apps and I find that it helps communicate intentions better. If you intend for some API to be "private" Angular now binds you to use this. This is consistent with the principle of least surprise; if you refactor some method names that are "private," you know your template does not rely on these APIs.
Our policy is that RCs can have breaking changes. Yes we could keep doing RCs until everything "stabilizes," but we found that doing that in the past meant slower releases. Remember that no one is forcing you to update to If you want to know about breaking changes earlier, you can subscribe to AngularJS's Github notifications. All major changes like this are discussed publicly via PR, just like this one. |
One simple feature which breaks with this new change is the ng-repeat 'track by' functionality when using MongoDb.
This is the most common use case for me and unfortunately will not work with the final version without resorting to unnecessary work around's/applying new naming conventions. |
Just need to comment out lines 9097 thru 9101 (5 lines) of angular.js 1.2.0.
Or this snippet on the minified version:
Someone gave the tip here: http://docs.angularjs.org/error/$parse:isecprv |
i shall be forking and making a bower package that does this for you. |
It just needs to be taken out. I don't understand the rationale for this. |
Can we start a petition on whitehouse.gov? |
Why the hell would you put something like that from a RC to a release ?! |
See #4926 |
@petebacondarwin So what version will this be disabled by default from? |
@kzar Just check the milestone of the PR: |
Guys, sorry for the troubles this change caused. This "feature" was mostly for people using Closure compiler and Google JS style, but I didn't realize that many people were relying on accessing We gonna release 1.2.1 (which will contain this "fix") within next days. The main outcome of this issue is: we should not put any breaking changes into RC. |
Thanks @vojtajina! 👍 |
I think the problem is not so much with templates where the change is not unreasonable (I'm not sure I still think it's worth it but that's kind of beside the point). The big deal is that use of I have some use of
I don't think a breaking change in an RC is necessarily a problem. A breaking change in the final release that wasn't in any RC is. |
@robfletcher actually it's best to completely avoid putting breaking features even between RCs. A release candidate actually means "Hey, folks, we think this should be the final version if we don't find any new unknown bugs. Please drop it into your apps and if we don't find any new bugs, it's going to be the final version" |
@robfletcher got a point 👍 / I'm moving back to rc-3 so I can play with $interval and use _id along // long live mongodb. |
@cburgdorf I agree but I think the screaming would have been a notch lower in volume if this had been done in an RC4 rather than final. |
@robfletcher absolutely :) |
Thank you! This made dynamic binding impossible (if a user entered something with an _ which was then used to look up something in a map later.) |
@vojtajina This sounds like the right thing to do considering the "shitstorm" of angry users. Thank you for making this decision! |
Thank you! I will wait for 1.2.1. There was just too much overhead to adjust for such a change. I had to change the entire Angular codebase, and use a function to return the private object. I couldn't do that in Angular expressions, so the next thing I would have had to change was all the APIs and traverse through every object just to remove the underscore in "_id". |
excellent news! awesome. |
Thank you very much! |
Thanks for the fix. I see the 1.2.1 release, but bower does not yet have it? Am I too impatient? |
@Valorum it should work if the tag is there |
Let me start by apologizing for not understanding the inner workings completely. When I ask bower about angular, this is what I get (after clearing Bower's cache):
https://github.com/angular/bower-angular/releases does not list 1.2.1. I do not know how this normally finds its way into Bower. I assume somebody needs to do something for this to happen? |
1.2.1 hasn't been deployed yet, AFAIK. |
I am not very clear if 1.2.1 is out yet. I see the version reflected in the documentation, but I don't see it available for download. The inconsistency is very confusing. |
It's getting there. Patience is a virtue :-) Wait for the blog posting. On 15 November 2013 19:23, Douglas Mak [email protected] wrote:
|
@vojtajina Thank you Vojta, much appreciated. |
Thanks for listening to the community. I'm using MongoDB for several apps and this was a pain. Theses multiple RC's had quite shaken my confidence as I've wasted a ton of time fighting broken changes between theses releases (ngAnimate, $parse). In my opinion, that should never ever happen during RC stage, especially if you use an unstable branch: RC means ready for production to me, like it's time to try to port my apps to it. If it's not ready, it should be called an alpha/beta. I'm looking forward to finally work with this new branch ;-). Kudos for the hard work. |
BREAKING CHANGE:
This commit introduces the notion of "private" properties (properties
whose names begin and/or end with an underscore) on the scope chain.
These properties will not be available to Angular expressions (i.e. {{
}} interpolation in templates and strings passed to
$parse
) They arefreely available to JavaScript code (as before).
Motivation
Angular expressions execute in a limited context. They do not have
direct access to the global scope, Window, Document or the Function
constructor. However, they have direct access to names/properties on
the scope chain. It has been a long standing best practice to keep
sensitive APIs outside of the scope chain (in a closure or your
controller.) That's easier said that done for two reasons: (1)
JavaScript does not have a notion of private properties so if you need
someone on the scope chain for JavaScript use, you also expose it to
Angular expressions, and (2) the new "controller as" syntax that's now
in increased usage exposes the entire controller on the scope chain
greatly increaing the exposed surface. Though Angular expressions are
written and controlled by the developer, they (1) typically deal with
user input and (2) don't get the kind of test coverage that JavaScript
code would. This commit provides a way, via a naming convention, to
allow publishing/restricting properties from controllers/scopes to
Angular expressions enabling one to only expose those properties that
are actually needed by the expressions.