Watcher's mustache implementation does not appear to HTML escape values

According to https://mustache.github.io/mustache.5.html:
All variables are HTML escaped by default.

I have some values that have angle-brackets in them (e.g. <foo>). But they won't render in an email body because it doesn't get html escaped and therefore get sanitized.

Here's a concrete example:

POST _watcher/watch/_execute
{
  "alternative_input" : {"foo": "<Bar>"},
  "action_modes" : {
    "_all" : "simulate" 
  },
  "watch" : {
    "trigger" : { "schedule" : { "interval" : "10s" } },
    "condition" : {
      "compare" : { "ctx.payload.foo" : { "eq" : "<Bar>" }}
    },
    "actions" : {
      "log_error" : {
        "logging" : {
          "text" : "Found {{ctx.payload.foo}} errors in the logs"
        }
      },
      "email_error" : {
        "email": {
              "to": "'John Doe <john.doe@example.com>'", 
              "subject": "{{ctx.watch_id}} executed", 
              "body": {
                "html" : "{{ctx.watch_id}} executed with {{ctx.payload.foo}} hits" 
              }
        }
      }
    }
  }
}

Response:

{
   "_id": "_inlined__0-2015-09-25T20:36:10.035Z",
   "watch_record": {
      "watch_id": "_inlined_",
      "state": "executed",
      "trigger_event": {
         "type": "manual",
         "triggered_time": "2015-09-25T20:36:10.035Z",
         "manual": {
            "schedule": {
               "scheduled_time": "2015-09-25T20:36:10.035Z"
            }
         }
      },
      "input": {
         "none": {}
      },
      "condition": {
         "compare": {
            "ctx.payload.foo": {
               "eq": "<Bar>"
            }
         }
      },
      "messages": [],
      "result": {
         "execution_time": "2015-09-25T20:36:10.035Z",
         "execution_duration": 20,
         "input": {
            "type": "simple",
            "status": "success",
            "payload": {
               "foo": "<Bar>"
            }
         },
         "condition": {
            "type": "compare",
            "status": "success",
            "met": true,
            "compare": {
               "resolved_values": {
                  "ctx.payload.foo": "<Bar>"
               }
            }
         },
         "actions": [
            {
               "id": "log_error",
               "type": "logging",
               "status": "simulated",
               "logging": {
                  "logged_text": "Found <Bar> errors in the logs"
               }
            },
            {
               "id": "email_error",
               "type": "email",
               "status": "simulated",
               "email": {
                  "message": {
                     "id": "_inlined__0-2015-09-25T20:36:10.035Z",
                     "sent_date": "2015-09-25T20:36:10.055Z",
                     "to": [
                        "'John Doe <john.doe@example.com>"
                     ],
                     "subject": "_inlined_ executed",
                     "body": {
                        "html": "_inlined_ executed with  hits"
                     }
                  }
               }
            }
         ]
      }
   }
}

Hey,

I tracked this down to the custom MustacheFactory used in Elasticsearch, and created an issue in core: https://github.com/elastic/elasticsearch/issues/13959

Thanks for pointing it out!

--Alex