ERB In Connector Expressions
In previous versions of Kinetic Task connectors could use erb tags (embedded ruby) to build dynamic expressions. Once the erb tags were resolved the task engine would evaluate the resulting expression (to true or false) and that would determine whether or not to continue down that branch.
To illustrate the various way erb tags could be used in connectors see the following examples.
"<%= @answers['Hard Drive Size'] %>" == "64GB"
In this example the task engine would first evaluate the ruby code within the erb tags, in this case the code evaluated at this point is simply the binding variable:
@answers['Hard Drive Size']
This simple ruby code would return the answer of the Hard Drive Size question and that would be placed inside the final expression. That final expression would look something like the following:
"32GB" == "64GB"
Then the task engine would evaluate this expression to determine whether or not to continue down the branch (in this case the expression evaluates to false).
There are essentially two phases of evaluation, erb tags and then the rest of the expression. Below, the portion in red is evaluated first and the rest in yellow is evaluated second.
"<%= @answers['Hard Drive Size'] %>" == "64GB"
Below is another way of writing the same expression but everything is within the erb tags.
<%= @answers['Hard Drive Size'] == "64GB" %>
When the erb tags are evaluated the answer is compared to the value and result in an expression that looks like this.
Then the engine evaluates this expression, and since the expression is just "true" it will evaluate to true.
In this example, while there are technically two phases of evaluation, it is as if there is only one because the second phase essentially has no effect (evaluating "true" returns true and evaluating "false" returns false).
Finally, it is worth noting that the same expression could be written like this, without any erb tags.
@answers['Hard Drive Size'] == "64GB"
In this example the first phase of evaluation would do nothing because there are no erb tags. In the second phase of evaluation we compare the answer to the 64GB value which will return true or false.
The potential vulnerability that forced us to change how connectors work is demonstrated by Example 1 above, where an author has created an expression that can be evaluated based on user input outside of what the author intended.
Why are the other two safe?
Example 2 is safe because the answer is compared to another string within erb tags, the result of evaluating the erb tags in Example 2 will result in either true or false. Because the answer is compared to another value within erb tags and it is not returned by the erb tags to be a part of the expression, the text of answer will not be evaluated on the second phase.
We know that Example 3 is safe because there is only one phase of evaluation, since there are no erb tags. In this example the text of the answer is compared to the other value, any malicious code within would not be evaluated.
What did we change to check expressions?
To better check for this potential vulnerability we removed the ability to use erb tags within connectors as they were this source of this vulnerability and the same functionality could easily be achieved without them. If an old connector is encountered with an erb tag the task engine will raise an error. Also the tree builder will not allow you to save connectors with expressions that contain erb tags.
In addition to being a potential vulnerability, Example 1 is prone to other issues. If the answer to the Hard Drive Size question contains a quote, newline, or number another special characters, the expression will likely raise an exception when evaluated when it is probably expected to just return false.
Updating Existing Connectors
This change was necessary but does require some changes to many connector expressions, depending on how a particular customer implemented our software. In this section we discuss the guidelines for removing erb tags from connector expressions. Below are the few common patterns
Starting with the easiest ones, connector expressions that do not contain any erb tags do not need to be modified.
The first pattern we will take a look at is the kind that contains the vulnerability. In this expression the text of the answer was actually evaluated.
"<%= @answers['Department'] %>" == "Human Resources"
To convert this expression to an equivalent without erb tags we need to remove the erb tags (obviously) as well as the surrounding quotes.
@answers['Department'] == "Human Resources"
Here is a similar expression that compares numbers rather than strings.
<%= @answers['Number'] %> > 1
The conversion in this case is a little different because there are no quotes around the erb tags because numbers in ruby do not have quotes. In this case we just remove the erb tags.
@answers['Number'] > 1
Another common pattern was to have multiple comparisons like above join with and/or.
"<%= @answers['Department'] %>" == "Human Resources" || "<%= @answers['Department'] %>" == "Management"
Converting these is the same as above we just need to make sure to replace each occurrence of the erb tags and surrounding quotes.
@answers['Department'] == "Human Resources" || @answers['Department'] == "Management"
Another pattern that is probably even more common is connector expressions where everything is surrounded by one set of erb tags.
<%= @answers['Department'] == "Human Resources" %>
To convert the expression above we simply need to remove the surround erb tags.
@answers['Department'] == "Human Resources"
In that example we are doing a single comparison but even if the expression within the erb tags is much more complex, converting the expression is the same, just remove the surrounding erb tags.
<%= @answers['Department'] == "Human Resources" && @answers['Department'] == "Management" %>
Sometimes there was an exclamation point before the erb tags, which negates the entire expression.
!<%= @answers['Department'] == "Human Resources" %>
We need to be a little careful when converting this because simply removing the erb tags like shown below does not result in an equivalent expression
!@answers['Department'] == "Human Resources"
The !@answers['Department'] portion will evaluate to true or false which will never be equal to "Human Resources". To convert expressions like this we essentially need to put parenthesis where the erb tags were. This ensures that the last operation will be the negation, which is how it was working with the erb tags.
!(@answers['Department'] == "Human Resources")
While the example above is a little silly because the == could just be changed to != there are many other cases where the ruby code within is more complicated and the easiest thing to do is replace the erb tags with parenthesis.
Multiple Erb Tags
The final common pattern we identified is the case where expressions have multiple sets of erb tags joined with and/or.
<%= @answers['Department'] == "Human Resources" %> && <%= @answers['Department'] == "Management" %>
To convert this specific expression we can simply remove the erb tags resulting in:
@answers['Department'] == "Human Resources" && @answers['Department'] == "Management"
But like the previous pattern (Everything Inside) sometimes the ruby code within is more complicated and the easiest way to do the conversion in these cases is just replace the erb tags with parenthesis.
(@answers['Department'] == "Human Resources") && (@answers['Department'] == "Management")
Unlikely Edge Case
In the two previous patterns it seems like the conversion process is automatic, just replace erb tags with parenthesis and it will behave the same exact way. While this is true for all of the expressions shown above there are some cases where the behavior does not remain the same. These are cases that relied on the two-phase evaluation to work properly, one such possible expression is shown below.
<%= @answers['Phone Required'] %>
Suppose the potential answers to the Phone Required are "true" and "false". When the erb tags are resolved we will get either the string "true" or the string "false". Then upon final evaluation "true" evaluates to true and "false" evaluates to false.
But if the expression is modified by removing its erb tags leaving the following the behavior will be different.
When this expression is evaluated the answer will either be the string "true" or "false", but because it is not a nil value or actually the false literal the expression will evaluate to true regardless of the content of the string.
To get the expression to work as before we would want to compare the answer of the Phone Required question to the string "true". This would return true when the answer is "true" and false when the answer is "false".
@answers['Phone Required'] == "true"
While we think patterns like this are extremely rare, it is something we need to consider when converting these connector expressions.
We know that this change affects many connectors and trees. To make this change easier to deal with we have built a utility that does its best to automatically convert the connector expressions it can. It essentially scans every tree in the Kinetic Task environment and scans each connector to see if it matches certain patterns it can convert. Connectors that match these patterns can be converted automatically. For connectors that do not, it provides a way to define the replacement expression without going into each tree and locate the specific connector.
For more see the Tree Converter article.