Dealing with Media Request Protection errors

Since 7.5, Sitecore introduced Media Request Protection that is essentially a hash added to some URL’s. Some people find this annoying, but I think this was a good move. If someone would just hammer your site by downloading media images with random width and height parameters, it would cause a lot of image resizing and consequently eat up all CPU.

The idea behind the Media Request Protection is to avoid these kind of denial of service attacks, by having a hash parameter on the URL. On an incoming media request, Sitecore calculates the hash of the request query string parameters and compares it with the given hash. If they are equal, Sitecore performs the resizing options (or whatever parameters are provided). If they are not equal, Sitecore will just send the original file as-is.

Error messages in the log like this one indicates there is something wrong with the media request protection:

ERROR MediaRequestProtection: An invalid/missing hash value was encountered. The expected hash value: ~40 hex digits. Media URL: /-/media/...., Referring URL: ....

You should monitor your log files for this error. Your site might look correct, but your visitors might be downloading non-resized images adding page weight. The most common reason for this error is an incorrect created media URL. If you create the URL by yourself, ensure to call HashingUtils.ProtectAssetUrl(mediaUrl).

One reason for this to occur, is a known bug in Sitecore Rich Text Editor. When you link a document, such as a PDF, in a RTE field, Sitecore adds the language parameter to the URL. Essentially, the rendered URL becomes something like this: /-/media/....document.pdf?la=en-GB. The “la” parameter triggers hash evaluation, despite the document not being a manipulable image. A workaround for this can be to remove the language parameter from the protected parameter list, like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <mediaLibrary>
      <requestProtection>
        <protectedMediaQueryParameters>
          <parameter description="language code" name="la">
            <patch:delete />
          </parameter>
        </protectedMediaQueryParameters>
      </requestProtection>
    </mediaLibrary>
  </sitecore>
</configuration>

If you add new functionality to your media processing pipelines that uses its own parameters, such as cropping, image format transformation etc, ensure to add those parameters accordingly to this <protectedMediaQueryParameters> list as well.

Moving forward
So let’s say you’ve fixed all hashing errors, you may see these errors in the log file anyway. Search engines etc have probably picked up the faulty URL. The protection only prevents hammering on the server, so an incorrect hash serves the original file with a 200 OK. To make this go away, we can add a noindex header for those URL’s. We can do this by adding the HTTP header “X-Robots-Tag: noindex“.

You can override the MediaRequestHandler and send the header when the hash is missing or incorrect, like this:

protected virtual void SendRobotsTag(MediaOptions options, HttpContext context)
{
	if (!IsRawUrlSafe)
	{
		var headers = context.Response.Headers;
		headers["X-Robots-Tag"] = "noindex";
	}
}

// Concept copied from MediaRequest
private bool IsRawUrlSafe
{
	get
	{
		if (!Settings.Media.RequestProtection.Enabled)
			return true;

		var rawUrl = HttpContext.Current.Request.RawUrl;
		return !ProtectRequest(Context.Site) ||
				HashingUtils.IsSafeUrl(rawUrl) ||
				HashingUtils.IsUrlHashValid(rawUrl);
	}
}

// Copied from MediaRequest
private bool ProtectRequest(SiteContext site)
{
	if (!Settings.Media.RequestProtection.Enabled)
		return false;
	if (site == null)
		return true;
	return !MediaManager.Config.RequestProtection.SitesToIgnore.Contains(site.Name.ToLowerInvariant());
}

Then add the call to SendRobotsTag after the SendMediaHeaders calls in the DoProcessRequest method.

A cleaner way of doing this would be to override the MediaRequest. Despite having nice virtual methods, it is still quite hard to override this class without a lot of code duplication because its Clone method prevents this.