The data I'm sending is not structured and does not contain any host field. This field is injected by Logstash's TCP input.
The behaviour is documented and well understood. Like I said, what I don't understand is what specific behaviour of Elasticsearch prevents the injection of this field, not where the field is coming from.
As a workaround, I'm simply renaming the field in my pipeline as follows, but I'm still a bit frustrated not to be able to find out the source of the issue.
filter {
    if [host] and ![host][name] {
        mutate {
            rename => { "[host]" => "[host][name]" }
        }
    }
}