Bug 1500360 - P1. Add telemetry to record the reason download protection allow or block the download. r=francois
authordlee <dlee@mozilla.com>
Wed, 28 Nov 2018 14:07:03 +0000
changeset 504959 db270ae8003b5eaac4658768af0b5f63369a942a
parent 504958 8692990356a7e39a67ee65909ff01f621d0830fb
child 504960 dc40390fd43656344b774aa2ba05caa2ebfa14ab
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfrancois
bugs1500360
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1500360 - P1. Add telemetry to record the reason download protection allow or block the download. r=francois In this patch, we added a telemetry to record different reasons that why download protection service decides to allow or block this download. The |verdict| and |shouldBlock| parameters are moved inside PendingLookup::OnComplete and now we used |Reason| in the query reputation code path instead Use enumerated Telemetry because it is easier to add more reasons in the future. Differential Revision: https://phabricator.services.mozilla.com/D10952
toolkit/components/reputationservice/ApplicationReputation.cpp
toolkit/components/telemetry/Histograms.json
--- a/toolkit/components/reputationservice/ApplicationReputation.cpp
+++ b/toolkit/components/reputationservice/ApplicationReputation.cpp
@@ -88,16 +88,40 @@ using safe_browsing::ClientDownloadReque
 
 // MOZ_LOG=ApplicationReputation:5
 mozilla::LazyLogModule ApplicationReputationService::prlog("ApplicationReputation");
 #define LOG(args) MOZ_LOG(ApplicationReputationService::prlog, mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(ApplicationReputationService::prlog, mozilla::LogLevel::Debug)
 
 enum class LookupType { AllowlistOnly, BlocklistOnly, BothLists };
 
+// Define the reasons that download protection service accepts or blocks this download.
+// This is now used for telemetry purposes and xpcshell test. Please also update the
+// xpcshell-test if a reason is added.
+//
+// LocalWhitelist       : URL is found in the local whitelist
+// LocalBlocklist       : URL is found in the local blocklist
+// NonBinary            : The downloaded non-binary file is not found in the local blocklist
+// VerdictSafe          : Remote lookup reports the download is safe
+// VerdictUnknown       : Remote lookup reports unknown, we treat this as a safe download
+// VerdictDangerous     : Remote lookup reports the download is dangerous
+// VerdictDangerousHost : Remote lookup reports the download is from a dangerous host
+// VerdictUnwanted      : Remote lookup reports the download is potentially unwatned
+// VerdictUncommon      : Remote lookup reports the download is uncommon
+// VerdictUnrecognized  : The verdict type from remote lookup is not defined in the csd.proto
+// DangerousPrefOff     : The download is dangerous, but the corresponding preference is off
+// DangerousHostPrefOff : The download is from a dangerous host, but the corresponding preference is off
+// UnwantedPrefOff      : The download is potentially unwanted, but the corresponding preference is off
+// UncommonPrefOff      : The download us uncommon, but the coressponding preference is off
+// NetworkError         : There is an error while requesting remote lookup
+// RemoteLookupDisabled : Remote lookup is disabled or the remote lookup URL is empty
+// InternalError        : An unexpected internal error
+// DPDisabled           : Download protection is disabled
+using Reason = mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_REASON;
+
 class PendingDBLookup;
 
 // A single use class private to ApplicationReputationService encapsulating an
 // nsIApplicationReputationQuery and an nsIApplicationReputationCallback. Once
 // created by ApplicationReputationService, it is guaranteed to call mCallback.
 // This class is private to ApplicationReputationService.
 class PendingLookup final : public nsIStreamListener,
                             public nsITimerCallback,
@@ -183,26 +207,25 @@ private:
   // The clock records the start time of a remote lookup request, used by telemetry.
   PRIntervalTime mTelemetryRemoteRequestStartMs;
 
   // Returns the type of download binary for the file.
   ClientDownloadRequest::DownloadType GetDownloadType(const nsACString& aFilename);
 
   // Clean up and call the callback. PendingLookup must not be used after this
   // function is called.
-  nsresult OnComplete(bool shouldBlock, nsresult rv,
-    uint32_t verdict = nsIApplicationReputationService::VERDICT_SAFE);
+  nsresult OnComplete(uint32_t aVerdict, Reason aReason, nsresult aRv);
 
   // Wrapper function for nsIStreamListener.onStopRequest to make it easy to
   // guarantee calling the callback
   nsresult OnStopRequestInternal(nsIRequest *aRequest,
                                  nsISupports *aContext,
                                  nsresult aResult,
-                                 bool* aShouldBlock,
-                                 uint32_t* aVerdict);
+                                 uint32_t& aVerdict,
+                                 Reason& aReason);
 
   // Return the hex-encoded hash of the whole URI.
   nsresult GetSpecHash(nsACString& aSpec, nsACString& hexEncodedHash);
 
   // Strip url parameters, fragments, and user@pass fields from the URI spec
   // using nsIURL. Hash data URIs and return blob URIs unfiltered.
   nsresult GetStrippedSpec(nsIURI* aUri, nsACString& spec);
 
@@ -246,17 +269,17 @@ private:
   // from which the file was downloaded.
   nsresult LookupNext();
 
   // Sends a query to the remote application reputation service. Returns NS_OK
   // on success.
   nsresult SendRemoteQuery();
 
   // Helper function to ensure that we always call the callback.
-  nsresult SendRemoteQueryInternal();
+  nsresult SendRemoteQueryInternal(Reason& aReason);
 };
 
 // A single-use class for looking up a single URI in the safebrowsing DB. This
 // class is private to PendingLookup.
 class PendingDBLookup final : public nsIUrlClassifierCallback
 {
 public:
   NS_DECL_ISUPPORTS
@@ -372,18 +395,18 @@ PendingDBLookup::HandleEvent(const nsACS
   // 2) PendingLookup::LookupNext if the URL does not match the blocklist.
   // Blocklisting trumps allowlisting.
   nsAutoCString blockList;
   Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, blockList);
   if ((mLookupType != LookupType::AllowlistOnly) && FindInReadable(blockList, tables)) {
     mPendingLookup->mBlocklistCount++;
     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST);
     LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this));
-    return mPendingLookup->OnComplete(true, NS_OK,
-      nsIApplicationReputationService::VERDICT_DANGEROUS);
+    return mPendingLookup->OnComplete(nsIApplicationReputationService::VERDICT_DANGEROUS,
+                                      Reason::LocalBlocklist, NS_OK);
   }
 
   nsAutoCString allowList;
   Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, allowList);
   if ((mLookupType != LookupType::BlocklistOnly) && FindInReadable(allowList, tables)) {
     mPendingLookup->mAllowlistCount++;
     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, ALLOW_LIST);
     LOG(("Found principal %s on allowlist [this = %p]", mSpec.get(), this));
@@ -883,17 +906,18 @@ PendingLookup::LookupNext()
     RefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
     return lookup->LookupSpec(spec, LookupType::BlocklistOnly);
   }
 
   // Now that we've looked up all of the URIs against the blocklist,
   // if any of mAnylistSpecs or mAllowlistSpecs matched the allowlist,
   // go ahead and pass.
   if (mAllowlistCount > 0) {
-    return OnComplete(false, NS_OK);
+    return OnComplete(nsIApplicationReputationService::VERDICT_SAFE,
+                      Reason::LocalWhitelist, NS_OK);
   }
 
   MOZ_ASSERT_IF(!mIsBinaryFile, mAllowlistSpecs.Length() == 0);
 
   // Only binary signatures remain.
   index = mAllowlistSpecs.Length() - 1;
   if (index >= 0) {
     spec = mAllowlistSpecs[index];
@@ -923,22 +947,24 @@ PendingLookup::LookupNext()
   } else if (mIsBinaryFile) {
     AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_BINARY_ARCHIVE::OtherBinaryFile);
   }
 
   // There are no more URIs to check against local list. If the file is
   // not eligible for remote lookup, bail.
   if (!mIsBinaryFile) {
     LOG(("Not eligible for remote lookups [this=%p]", this));
-    return OnComplete(false, NS_OK);
+    return OnComplete(nsIApplicationReputationService::VERDICT_SAFE,
+                      Reason::NonBinaryFile, NS_OK);
   }
 
   nsresult rv = SendRemoteQuery();
   if (NS_FAILED(rv)) {
-    return OnComplete(false, rv);
+    return OnComplete(nsIApplicationReputationService::VERDICT_SAFE,
+                      Reason::InternalError, rv);
   }
   return NS_OK;
 }
 
 nsCString
 PendingLookup::EscapeCertificateAttribute(const nsACString& aAttribute)
 {
   // Escape '/' because it's a field separator, and '%' because Chrome does
@@ -1122,17 +1148,18 @@ PendingLookup::AddRedirects(nsIArray* aR
 }
 
 nsresult
 PendingLookup::StartLookup()
 {
   mStartTime = TimeStamp::Now();
   nsresult rv = DoLookupInternal();
   if (NS_FAILED(rv)) {
-    return OnComplete(false, NS_OK);
+    return OnComplete(nsIApplicationReputationService::VERDICT_SAFE,
+                      Reason::InternalError, NS_OK);
   }
   return rv;
 }
 
 nsresult
 PendingLookup::GetSpecHash(nsACString& aSpec, nsACString& hexEncodedHash)
 {
   nsresult rv;
@@ -1293,45 +1320,76 @@ PendingLookup::DoLookupInternal()
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Start the call chain.
   return LookupNext();
 }
 
 nsresult
-PendingLookup::OnComplete(bool shouldBlock, nsresult rv, uint32_t verdict)
+PendingLookup::OnComplete(uint32_t aVerdict, Reason aReason, nsresult aRv)
 {
-  MOZ_ASSERT(!shouldBlock ||
-             verdict != nsIApplicationReputationService::VERDICT_SAFE);
-
-  if (NS_FAILED(rv)) {
+  if (NS_FAILED(aRv)) {
     nsAutoCString errorName;
-    mozilla::GetErrorName(rv, errorName);
+    mozilla::GetErrorName(aRv, errorName);
     LOG(("Failed sending remote query for application reputation "
          "[rv = %s, this = %p]", errorName.get(), this));
   }
 
   if (mTimeoutTimer) {
     mTimeoutTimer->Cancel();
     mTimeoutTimer = nullptr;
   }
 
+  bool shouldBlock = true;
+  switch (aVerdict) {
+    case nsIApplicationReputationService::VERDICT_DANGEROUS:
+      if (!Preferences::GetBool(PREF_BLOCK_DANGEROUS, true)) {
+        shouldBlock = false;
+        aReason = Reason::DangerousPrefOff;
+      }
+      break;
+    case nsIApplicationReputationService::VERDICT_UNCOMMON:
+      if (!Preferences::GetBool(PREF_BLOCK_UNCOMMON, true)) {
+        shouldBlock = false;
+        aReason = Reason::UncommonPrefOff;
+      }
+      break;
+    case nsIApplicationReputationService::VERDICT_POTENTIALLY_UNWANTED:
+      if (!Preferences::GetBool(PREF_BLOCK_POTENTIALLY_UNWANTED, true)) {
+        shouldBlock = false;
+        aReason = Reason::UnwantedPrefOff;
+      }
+      break;
+    case nsIApplicationReputationService::VERDICT_DANGEROUS_HOST:
+      if (!Preferences::GetBool(PREF_BLOCK_DANGEROUS_HOST, true)) {
+        shouldBlock = false;
+        aReason = Reason::DangerousHostPrefOff;
+      }
+      break;
+    default:
+      shouldBlock = false;
+      break;
+  }
+
+  AccumulateCategorical(aReason);
   Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK,
-    shouldBlock);
+             shouldBlock);
+
   double t = (TimeStamp::Now() - mStartTime).ToMilliseconds();
   LOG(("Application Reputation verdict is %u, obtained in %f ms [this = %p]",
-       verdict, t, this));
+       aVerdict, t, this));
   if (shouldBlock) {
     LOG(("Application Reputation check failed, blocking bad binary [this = %p]",
         this));
   } else {
     LOG(("Application Reputation check passed [this = %p]", this));
   }
-  nsresult res = mCallback->OnComplete(shouldBlock, rv, verdict);
+
+  nsresult res = mCallback->OnComplete(shouldBlock, aRv, aVerdict);
   return res;
 }
 
 nsresult
 PendingLookup::ParseCertificates(nsIArray* aSigArray)
 {
   // If we haven't been set for any reason, bail.
   NS_ENSURE_ARG_POINTER(aSigArray);
@@ -1391,39 +1449,48 @@ PendingLookup::ParseCertificates(nsIArra
     mRequest.mutable_signature()->set_trusted(true);
   }
   return NS_OK;
 }
 
 nsresult
 PendingLookup::SendRemoteQuery()
 {
-  nsresult rv = SendRemoteQueryInternal();
+  Reason reason = Reason::NotSet;
+  nsresult rv = SendRemoteQueryInternal(reason);
   if (NS_FAILED(rv)) {
-    return OnComplete(false, rv);
+    return OnComplete(nsIApplicationReputationService::VERDICT_SAFE,
+                      reason, rv);
   }
   // SendRemoteQueryInternal has fired off the query and we call OnComplete in
   // the nsIStreamListener.onStopRequest.
   return rv;
 }
 
 nsresult
-PendingLookup::SendRemoteQueryInternal()
+PendingLookup::SendRemoteQueryInternal(Reason& aReason)
 {
+  auto scopeExit = mozilla::MakeScopeExit([&aReason] () {
+    if (aReason == Reason::NotSet) {
+      aReason = Reason::InternalError;
+    }
+  });
+
   // If we aren't supposed to do remote lookups, bail.
   if (!Preferences::GetBool(PREF_SB_DOWNLOADS_REMOTE_ENABLED, false)) {
     LOG(("Remote lookups are disabled [this = %p]", this));
+    aReason = Reason::RemoteLookupDisabled;
     return NS_ERROR_NOT_AVAILABLE;
   }
   // If the remote lookup URL is empty or absent, bail.
   nsAutoCString serviceUrl;
-  NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, serviceUrl),
-                    NS_ERROR_NOT_AVAILABLE);
-  if (serviceUrl.IsEmpty()) {
-    LOG(("Remote lookup URL is empty [this = %p]", this));
+  if (NS_FAILED(Preferences::GetCString(PREF_SB_APP_REP_URL, serviceUrl)) ||
+      serviceUrl.IsEmpty()) {
+    LOG(("Remote lookup URL is empty or absent [this = %p]", this));
+    aReason = Reason::RemoteLookupDisabled;
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   LOG(("Sending remote query for application reputation [this = %p]",
        this));
   // We did not find a local result, so fire off the query to the
   // application reputation service.
   nsCOMPtr<nsIURI> uri;
@@ -1606,53 +1673,56 @@ PendingLookup::OnStartRequest(nsIRequest
 }
 
 NS_IMETHODIMP
 PendingLookup::OnStopRequest(nsIRequest *aRequest,
                              nsISupports *aContext,
                              nsresult aResult) {
   NS_ENSURE_STATE(mCallback);
 
-  bool shouldBlock = false;
-  uint32_t verdict = nsIApplicationReputationService::VERDICT_SAFE;
-
   if (aResult != NS_ERROR_NET_TIMEOUT) {
     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_REMOTE_LOOKUP_TIMEOUT,
       false);
 
     MOZ_ASSERT(mTelemetryRemoteRequestStartMs > 0);
     int32_t msecs =
       PR_IntervalToMilliseconds(PR_IntervalNow() - mTelemetryRemoteRequestStartMs);
 
     MOZ_ASSERT(msecs >= 0);
     mozilla::Telemetry::Accumulate(
       mozilla::Telemetry::APPLICATION_REPUTATION_REMOTE_LOOKUP_RESPONSE_TIME, msecs);
   }
 
-  nsresult rv = OnStopRequestInternal(aRequest, aContext, aResult,
-                                      &shouldBlock, &verdict);
-  OnComplete(shouldBlock, rv, verdict);
+  uint32_t verdict = nsIApplicationReputationService::VERDICT_SAFE;
+  Reason reason = Reason::NotSet;
+  nsresult rv = OnStopRequestInternal(aRequest, aContext, aResult, verdict, reason);
+  OnComplete(verdict, reason, rv);
   return rv;
 }
 
 nsresult
 PendingLookup::OnStopRequestInternal(nsIRequest *aRequest,
                                      nsISupports *aContext,
                                      nsresult aResult,
-                                     bool* aShouldBlock,
-                                     uint32_t* aVerdict) {
+                                     uint32_t& aVerdict,
+                                     Reason& aReason) {
+  auto scopeExit = mozilla::MakeScopeExit([&aReason] () {
+    // If |aReason| is not set while exiting, there must be an error.
+    if (aReason == Reason::NotSet) {
+      aReason = Reason::NetworkError;
+    }
+  });
+
   if (NS_FAILED(aResult)) {
     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
       SERVER_RESPONSE_FAILED);
     AccumulateCategorical(NSErrorToLabel(aResult));
     return aResult;
   }
 
-  *aShouldBlock = false;
-  *aVerdict = nsIApplicationReputationService::VERDICT_SAFE;
   nsresult rv;
   nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
   if (NS_FAILED(rv)) {
     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
       SERVER_RESPONSE_FAILED);
     AccumulateCategorical(
       mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_SERVER_2::FailGetChannel);
     return rv;
@@ -1689,33 +1759,43 @@ PendingLookup::OnStopRequestInternal(nsI
   AccumulateCategorical(
     mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_SERVER_2::ResponseValid);
 
   // Clamp responses 0-7, we only know about 0-4 for now.
   Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER_VERDICT,
     std::min<uint32_t>(response.verdict(), 7));
   switch(response.verdict()) {
     case safe_browsing::ClientDownloadResponse::DANGEROUS:
-      *aShouldBlock = Preferences::GetBool(PREF_BLOCK_DANGEROUS, true);
-      *aVerdict = nsIApplicationReputationService::VERDICT_DANGEROUS;
+      aVerdict = nsIApplicationReputationService::VERDICT_DANGEROUS;
+      aReason = Reason::VerdictDangerous;
       break;
     case safe_browsing::ClientDownloadResponse::DANGEROUS_HOST:
-      *aShouldBlock = Preferences::GetBool(PREF_BLOCK_DANGEROUS_HOST, true);
-      *aVerdict = nsIApplicationReputationService::VERDICT_DANGEROUS_HOST;
+      aVerdict = nsIApplicationReputationService::VERDICT_DANGEROUS_HOST;
+      aReason = Reason::VerdictDangerousHost;
       break;
     case safe_browsing::ClientDownloadResponse::POTENTIALLY_UNWANTED:
-      *aShouldBlock = Preferences::GetBool(PREF_BLOCK_POTENTIALLY_UNWANTED, false);
-      *aVerdict = nsIApplicationReputationService::VERDICT_POTENTIALLY_UNWANTED;
+      aVerdict = nsIApplicationReputationService::VERDICT_POTENTIALLY_UNWANTED;
+      aReason = Reason::VerdictUnwanted;
       break;
     case safe_browsing::ClientDownloadResponse::UNCOMMON:
-      *aShouldBlock = Preferences::GetBool(PREF_BLOCK_UNCOMMON, false);
-      *aVerdict = nsIApplicationReputationService::VERDICT_UNCOMMON;
+      aVerdict = nsIApplicationReputationService::VERDICT_UNCOMMON;
+      aReason = Reason::VerdictUncommon;
+      break;
+    case safe_browsing::ClientDownloadResponse::UNKNOWN:
+      aVerdict = nsIApplicationReputationService::VERDICT_SAFE;
+      aReason = Reason::VerdictUnknown;
+      break;
+    case safe_browsing::ClientDownloadResponse::SAFE:
+      aVerdict = nsIApplicationReputationService::VERDICT_SAFE;
+      aReason = Reason::VerdictSafe;
       break;
     default:
       // Treat everything else as safe
+      aVerdict = nsIApplicationReputationService::VERDICT_SAFE;
+      aReason = Reason::VerdictUnrecognized;
       break;
   }
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(ApplicationReputationService,
                   nsIApplicationReputationService)
@@ -1749,39 +1829,44 @@ ApplicationReputationService::QueryReput
     nsIApplicationReputationQuery* aQuery,
     nsIApplicationReputationCallback* aCallback) {
   LOG(("Starting application reputation check [query=%p]", aQuery));
   NS_ENSURE_ARG_POINTER(aQuery);
   NS_ENSURE_ARG_POINTER(aCallback);
 
   nsresult rv = QueryReputationInternal(aQuery, aCallback);
   if (NS_FAILED(rv)) {
+    Reason reason = rv == NS_ERROR_NOT_AVAILABLE ?
+      Reason::DPDisabled : Reason::InternalError;
+
+    AccumulateCategorical(reason);
     Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK,
       false);
+
     aCallback->OnComplete(false, rv,
                           nsIApplicationReputationService::VERDICT_SAFE);
   }
   return NS_OK;
 }
 
 nsresult ApplicationReputationService::QueryReputationInternal(
   nsIApplicationReputationQuery* aQuery,
   nsIApplicationReputationCallback* aCallback) {
-  nsresult rv;
+
   // If malware checks aren't enabled, don't query application reputation.
   if (!Preferences::GetBool(PREF_SB_MALWARE_ENABLED, false)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (!Preferences::GetBool(PREF_SB_DOWNLOADS_ENABLED, false)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsCOMPtr<nsIURI> uri;
-  rv = aQuery->GetSourceURI(getter_AddRefs(uri));
+  nsresult rv = aQuery->GetSourceURI(getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
   // Bail if the URI hasn't been set.
   NS_ENSURE_STATE(uri);
 
   // Create a new pending lookup and start the call chain.
   RefPtr<PendingLookup> lookup(new PendingLookup(aQuery, aCallback));
   NS_ENSURE_STATE(lookup);
 
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -122,16 +122,26 @@
     "alert_emails": ["francois@mozilla.com", "safebrowsing-telemetry@mozilla.org"],
     "bug_numbers": [1485180],
     "expires_in_version": "69",
     "releaseChannelCollection": "opt-out",
     "kind": "categorical",
     "labels": ["ValidHash", "OriginalHashEmpty", "OriginalHashTooShort", "OriginalHashTooLong", "MissingDigest", "MissingSha256", "InvalidSha256"],
     "description": "Whether the hash included in application reputation lookups is valid or broken in some way."
   },
+  "APPLICATION_REPUTATION_REASON": {
+    "record_in_processes": ["main", "content"],
+    "alert_emails": ["dlee@mozilla.com", "safebrowsing-telemetry@mozilla.org"],
+    "bug_numbers": [1500360],
+    "expires_in_version": "never",
+    "releaseChannelCollection": "opt-out",
+    "kind": "categorical",
+    "labels": ["NotSet", "LocalWhitelist", "LocalBlocklist", "NonBinaryFile", "VerdictSafe", "VerdictUnknown", "VerdictDangerous", "VerdictDangerousHost", "VerdictUnwanted", "VerdictUncommon", "VerdictUnrecognized", "DangerousPrefOff", "DangerousHostPrefOff", "UnwantedPrefOff", "UncommonPrefOff", "NetworkError", "RemoteLookupDisabled", "InternalError", "DPDisabled"],
+    "description": "The reason application reputation service blocks or allows the download."
+  },
   "APPLICATION_REPUTATION_SHOULD_BLOCK": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["francois@mozilla.com", "safebrowsing-telemetry@mozilla.org"],
     "expires_in_version": "never",
     "releaseChannelCollection": "opt-out",
     "kind": "boolean",
     "description": "Overall (local or remote) application reputation verdict (shouldBlock=false is OK)."
   },