Bug 1363546 - Store and report HSTS upgrade source r?keeler,mayhemer p=francois draft
authorKate McKinley <kmckinley@mozilla.com>
Tue, 23 May 2017 15:31:37 -0700
changeset 596827 50092885d3258251ae7071c0c0bb51c12330e132
parent 596660 72346a4d6afcc17504f911190dbf470100e879ec
child 596936 5868b8329d734494dca46900a9f132fb476394ec
push id64771
push userbmo:kmckinley@mozilla.com
push dateMon, 19 Jun 2017 21:11:50 +0000
reviewerskeeler, mayhemer
bugs1363546
milestone56.0a1
Bug 1363546 - Store and report HSTS upgrade source r?keeler,mayhemer p=francois Add a field to the HSTS cache which indicates the source of the HSTS entry if known, from the preload list, organically seen header, or HSTS priming, or unknown otherwise. Also adds telemetry to collect the source when upgrading in NS_ShouldSecureUpgrade. MozReview-Commit-ID: 3IwyYe3Cn73
docshell/base/nsDocShell.cpp
dom/ipc/ContentParent.cpp
dom/security/nsMixedContentBlocker.cpp
dom/security/test/hsts/browser_hsts-priming_allow_active.js
dom/security/test/hsts/browser_hsts-priming_allow_display.js
dom/security/test/hsts/browser_hsts-priming_block_active.js
dom/security/test/hsts/browser_hsts-priming_block_active_css.js
dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
dom/security/test/hsts/browser_hsts-priming_block_display.js
dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
dom/security/test/hsts/head.js
netwerk/base/nsNetUtil.cpp
netwerk/protocol/http/HSTSPrimerListener.cpp
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpHandler.cpp
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/nsISiteSecurityService.idl
security/manager/ssl/nsSiteSecurityService.cpp
security/manager/ssl/nsSiteSecurityService.h
security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
security/manager/ssl/tests/unit/test_forget_about_site_security_headers.js
security/manager/ssl/tests/unit/test_ocsp_must_staple.js
security/manager/ssl/tests/unit/test_ocsp_no_hsts_upgrade.js
security/manager/ssl/tests/unit/test_pinning_header_parsing.js
security/manager/ssl/tests/unit/test_sss_enumerate.js
security/manager/ssl/tests/unit/test_sss_eviction.js
security/manager/ssl/tests/unit/test_sss_originAttributes.js
security/manager/ssl/tests/unit/test_sss_savestate.js
security/manager/ssl/tests/unit/test_sts_fqdn.js
security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
security/manager/ssl/tests/unit/test_sts_parser.js
security/manager/ssl/tests/unit/test_sts_preload_dynamic.js
security/manager/ssl/tests/unit/test_sts_preloadlist_perwindowpb.js
security/manager/tools/getHSTSPreloadList.js
toolkit/components/telemetry/Histograms.json
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5026,20 +5026,21 @@ nsDocShell::DisplayLoadError(nsresult aE
           UsePrivateBrowsing() ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
         bool isStsHost = false;
         bool isPinnedHost = false;
         if (XRE_IsParentProcess()) {
           nsCOMPtr<nsISiteSecurityService> sss =
             do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
           NS_ENSURE_SUCCESS(rv, rv);
           rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI,
-                                flags, mOriginAttributes, nullptr, &isStsHost);
+                                flags, mOriginAttributes, nullptr, nullptr,
+                                &isStsHost);
           NS_ENSURE_SUCCESS(rv, rv);
           rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HPKP, aURI,
-                                flags, mOriginAttributes, nullptr,
+                                flags, mOriginAttributes, nullptr, nullptr,
                                 &isPinnedHost);
           NS_ENSURE_SUCCESS(rv, rv);
         } else {
           mozilla::dom::ContentChild* cc =
             mozilla::dom::ContentChild::GetSingleton();
           mozilla::ipc::URIParams uri;
           SerializeURI(aURI, uri);
           cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, flags,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -3555,17 +3555,17 @@ ContentParent::RecvIsSecureURI(const uin
   if (!sss) {
     return IPC_FAIL_NO_REASON(this);
   }
   nsCOMPtr<nsIURI> ourURI = DeserializeURI(aURI);
   if (!ourURI) {
     return IPC_FAIL_NO_REASON(this);
   }
   nsresult rv = sss->IsSecureURI(aType, ourURI, aFlags, aOriginAttributes, nullptr,
-                                 aIsSecureURI);
+                                 nullptr, aIsSecureURI);
   if (NS_FAILED(rv)) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvAccumulateMixedContentHSTS(const URIParams& aURI, const bool& aActive, const bool& aHSTSPriming,
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -919,17 +919,17 @@ nsMixedContentBlocker::ShouldLoad(bool a
   bool doHSTSPriming = false;
   if (IsEligibleForHSTSPriming(aContentLocation)) {
     bool hsts = false;
     bool cached = false;
     nsCOMPtr<nsISiteSecurityService> sss =
       do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aContentLocation,
-        0, originAttributes, &cached, &hsts);
+        0, originAttributes, &cached, nullptr, &hsts);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (hsts && sUseHSTS) {
       // assume we will be upgraded later
       Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
           (active) ? MixedContentHSTSPrimingState::eMCB_HSTS_ACTIVE_UPGRADE
                    : MixedContentHSTSPrimingState::eMCB_HSTS_PASSIVE_UPGRADE);
       *aDecision = ACCEPT;
@@ -1145,17 +1145,17 @@ nsMixedContentBlocker::AccumulateMixedCo
 
   bool hsts;
   nsresult rv;
   nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
   if (NS_FAILED(rv)) {
     return;
   }
   rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, 0,
-                        aOriginAttributes, nullptr, &hsts);
+                        aOriginAttributes, nullptr, nullptr, &hsts);
   if (NS_FAILED(rv)) {
     return;
   }
 
   // states: would upgrade, would prime, hsts info cached
   // active, passive
   //
   if (!aActive) {
--- a/dom/security/test/hsts/browser_hsts-priming_allow_active.js
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_active.js
@@ -4,16 +4,17 @@
  *   content is allowed.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 1,
       "failure": 2,
     },
   }
 };
--- a/dom/security/test/hsts/browser_hsts-priming_allow_display.js
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_display.js
@@ -4,16 +4,17 @@
  *   content is allowed.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 1,
       "failure": 2,
     },
   }
 };
--- a/dom/security/test/hsts/browser_hsts-priming_block_active.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active.js
@@ -4,16 +4,17 @@
  *   content is blocked.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 1,
       "failure": 2,
     },
   }
 };
--- a/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
@@ -4,16 +4,17 @@
  *   content is blocked for css.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 1,
       "failure": 2,
     },
   }
 };
--- a/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
@@ -4,16 +4,17 @@
  *   content is blocked and redirect to the same host should still upgrade.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 1,
       "failure": 2,
     },
   }
 };
--- a/dom/security/test/hsts/browser_hsts-priming_block_display.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_display.js
@@ -4,16 +4,17 @@
  *   content is blocked.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 1,
       "failure": 2,
     },
   }
 };
--- a/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
+++ b/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
@@ -27,19 +27,19 @@ add_task(function*() {
 
   SetupPrefTestEnvironment(which, [["security.mixed_content.hsts_priming_cache_timeout", 1]]);
   clear_hists(expected_telemetry);
 
   yield execute_test("no-ssl", test_settings[which].mimetype);
 
   let pre_promise = performance.now();
 
-  while ((performance.now() - pre_promise) < 2000) {
+  while ((performance.now() - pre_promise) < 1000) {
     yield new Promise(function (resolve) {
-      setTimeout(resolve, 2000);
+      setTimeout(resolve, 1000);
     });
   }
 
   // clear the fact that we saw a priming request
   test_settings[which].priming = {};
 
   yield execute_test("no-ssl", test_settings[which].mimetype);
   is(test_settings[which].priming["no-ssl"], true,
--- a/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
+++ b/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
@@ -4,16 +4,17 @@
  *   mixed-content blocks before HSTS.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,0,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 1,
       "failure": 2,
     },
   }
 };
--- a/dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
+++ b/dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
@@ -9,16 +9,17 @@
  *   subdomains.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 2,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 4,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,2,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 2,
     },
   }
 };
 
--- a/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
@@ -4,16 +4,17 @@
  *   than once.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 8,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,2,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 1,
       "failure": 2,
     },
   }
 };
--- a/dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
@@ -5,16 +5,17 @@
  *   subdomains.
  */
 'use strict';
 
 var expected_telemetry = {
   "histograms": {
     "MIXED_CONTENT_HSTS_PRIMING_RESULT": 1,
     "MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 3,
+    "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
   },
   "keyed-histograms": {
     "HSTS_PRIMING_REQUEST_DURATION": {
       "success": 1,
     },
   }
 };
 
--- a/dom/security/test/hsts/head.js
+++ b/dom/security/test/hsts/head.js
@@ -442,16 +442,17 @@ async function execute_test(test, mimety
 }
 
 /* Expected should look something like this:
  * The numbers are the sum of all telemetry values.
   var expected_telemetry = {
     "histograms": {
       "MIXED_CONTENT_HSTS_PRIMING_RESULT": 6,
       "HSTS_PRIMING_REQUESTS": 10,
+      "HSTS_UPGRADE_SOURCE": [ 0,0,2,0,0,0,0,0,0 ]
     },
     "keyed-histograms": {
       "HSTS_PRIMING_REQUEST_DURATION": {
         "success": 1,
         "failure": 2,
       },
     }
   };
@@ -468,18 +469,34 @@ function test_telemetry(expected) {
       continue;
     }
 
     if (!hs) {
       ok(false, "No histogram found for key " + key);
       continue;
     }
 
-    // there may have been other background requests processed
-    ok(hs.counts.reduce(sum) >= expected['histograms'][key], "Histogram counts match expected, got " + hs.counts.reduce(sum) + ", expected at least " + expected['histograms'][key]);
+    if (Array.isArray(expected['histograms'][key])) {
+      var is_ok = true;
+      if (expected['histograms'][key].length != hs.counts.length) {
+        ok(false, "Histogram lengths match");
+        continue;
+      }
+
+      for (let idx in expected['histograms'][key]) {
+        is_ok = (hs.counts[idx] >= expected['histograms'][key][idx]);
+        if (!is_ok) {
+          break;
+        }
+      }
+      ok(is_ok, "Histogram counts match for " + key + " - Got " + hs.counts + ", expected " + expected['histograms'][key]);
+    } else {
+      // there may have been other background requests processed
+      ok(hs.counts.reduce(sum) >= expected['histograms'][key], "Histogram counts match expected, got " + hs.counts.reduce(sum) + ", expected at least " + expected['histograms'][key]);
+    }
   }
 
   for (let key in expected['keyed-histograms']) {
     let hs = undefined;
     try {
       let hist = Services.telemetry.getKeyedHistogramById(key);
       hs = hist.snapshot();
       hist.clear();
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -2579,30 +2579,47 @@ NS_ShouldSecureUpgrade(nsIURI* aURI,
       }
     }
 
     // enforce Strict-Transport-Security
     nsISiteSecurityService* sss = gHttpHandler->GetSSService();
     NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
 
     bool isStsHost = false;
+    uint32_t hstsSource = 0;
     uint32_t flags = aPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
     rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags,
-                          aOriginAttributes, nullptr, &isStsHost);
+                          aOriginAttributes, nullptr, &hstsSource, &isStsHost);
 
     // if the SSS check fails, it's likely because this load is on a
     // malformed URI or something else in the setup is wrong, so any error
     // should be reported.
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (isStsHost) {
       LOG(("nsHttpChannel::Connect() STS permissions found\n"));
       if (aAllowSTS) {
         Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3);
         aShouldUpgrade = true;
+        switch (hstsSource) {
+          case nsISiteSecurityService::SOURCE_PRELOAD_LIST:
+              Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 0);
+              break;
+          case nsISiteSecurityService::SOURCE_ORGANIC_REQUEST:
+              Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 1);
+              break;
+          case nsISiteSecurityService::SOURCE_HSTS_PRIMING:
+              Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 2);
+              break;
+          case nsISiteSecurityService::SOURCE_UNKNOWN:
+          default:
+              // record this as an organic request
+              Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 1);
+              break;
+        }
         return NS_OK;
       } else {
         Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2);
       }
     } else {
       Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1);
     }
   } else {
--- a/netwerk/protocol/http/HSTSPrimerListener.cpp
+++ b/netwerk/protocol/http/HSTSPrimerListener.cpp
@@ -153,17 +153,17 @@ HSTSPrimingListener::CheckHSTSPrimingReq
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(uri, NS_ERROR_CONTENT_BLOCKED);
 
   OriginAttributes originAttributes;
   NS_GetOriginAttributes(httpChannel, originAttributes);
 
   bool hsts;
   rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0,
-                        originAttributes, nullptr, &hsts);
+                        originAttributes, nullptr, nullptr, &hsts);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (hsts) {
     // An HSTS upgrade was found
     return NS_OK;
   }
 
   // There is no HSTS upgrade available
@@ -222,35 +222,35 @@ HSTSPrimingListener::StartHSTSPriming(ns
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIURI> uri;
   rv = NS_GetSecureUpgradedURI(finalChannelURI, getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv,rv);
 
   // check the HSTS cache
   bool hsts;
-  bool cached;
+  bool hstsCached;
   nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   OriginAttributes originAttributes;
   NS_GetOriginAttributes(aRequestChannel, originAttributes);
 
   rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0,
-                        originAttributes, &cached, &hsts);
+                        originAttributes, &hstsCached, nullptr, &hsts);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (hsts) {
     // already saw this host and will upgrade if allowed by preferences
     Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
                           HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_HSTS);
     return aCallback->OnHSTSPrimingSucceeded(true);
   }
 
-  if (cached) {
+  if (hstsCached) {
     // there is a non-expired entry in the cache that doesn't allow us to
     // upgrade, so go ahead and fail early.
     Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
                           HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_NO_HSTS);
     return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
   }
 
   // Either it wasn't cached or the cached result has expired. Build a
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1791,19 +1791,23 @@ nsHttpChannel::ProcessSingleSecurityHead
     if (NS_SUCCEEDED(rv)) {
         nsISiteSecurityService* sss = gHttpHandler->GetSSService();
         NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
         // Process header will now discard the headers itself if the channel
         // wasn't secure (whereas before it had to be checked manually)
         OriginAttributes originAttributes;
         NS_GetOriginAttributes(this, originAttributes);
         uint32_t failureResult;
+        uint32_t headerSource = nsISiteSecurityService::SOURCE_ORGANIC_REQUEST;
+        if (mLoadInfo && mLoadInfo->GetIsHSTSPriming()) {
+            headerSource = nsISiteSecurityService::SOURCE_HSTS_PRIMING;
+        }
         rv = sss->ProcessHeader(aType, mURI, securityHeader, aSSLStatus,
-                                aFlags, originAttributes, nullptr, nullptr,
-                                &failureResult);
+                                aFlags, headerSource, originAttributes,
+                                nullptr, nullptr, &failureResult);
         if (NS_FAILED(rv)) {
             nsAutoString consoleErrorCategory;
             nsAutoString consoleErrorTag;
             switch (aType) {
                 case nsISiteSecurityService::HEADER_HSTS:
                     GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
                     consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
                     break;
@@ -8655,18 +8659,20 @@ nsHttpChannel::OnHSTSPrimingSucceeded(bo
 
     if (nsMixedContentBlocker::sUseHSTS) {
         // redirect the channel to HTTPS if the pref
         // "security.mixed_content.use_hsts" is true
         LOG(("HSTS Priming succeeded, redirecting to HTTPS [this=%p]", this));
         Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
                 (aCached) ? HSTSPrimingResult::eHSTS_PRIMING_CACHED_DO_UPGRADE :
                             HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED);
-        // we have to record this upgrade here
+        // we have to record this upgrade here since we have already
+        // been through NS_ShouldSecureUpgrade
         Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3);
+        Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 2);
         mLoadInfo->SetIsHSTSPrimingUpgrade(true);
         return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
     }
 
     // preserve the mixed-content-before-hsts order and block if required
     if (wouldBlock) {
         LOG(("HSTS Priming succeeded, blocking for mixed-content [this=%p]",
                     this));
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -2407,17 +2407,17 @@ nsHttpHandler::SpeculativeConnectInterna
         originAttributes = aPrincipal->OriginAttributesRef();
     } else if (loadContext) {
         loadContext->GetOriginAttributes(originAttributes);
     }
 
     nsCOMPtr<nsIURI> clone;
     if (NS_SUCCEEDED(sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS,
                                       aURI, flags, originAttributes,
-                                      nullptr, &isStsHost)) &&
+                                      nullptr, nullptr, &isStsHost)) &&
                                       isStsHost) {
         if (NS_SUCCEEDED(NS_GetSecureUpgradedURI(aURI,
                                                  getter_AddRefs(clone)))) {
             aURI = clone.get();
             // (NOTE: We better make sure |clone| stays alive until the end
             // of the function now, since our aURI arg now points to it!)
         }
     }
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -495,27 +495,29 @@ CertErrorRunnable::OverrideAllowedForHos
             ("[%p][%p] Creating new URI failed", mFdForLogging, this));
     return rv;
   }
   rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS,
                         uri,
                         mProviderFlags,
                         mInfoObject->GetOriginAttributes(),
                         nullptr,
+                        nullptr,
                         &strictTransportSecurityEnabled);
   if (NS_FAILED(rv)) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
             ("[%p][%p] checking for HSTS failed", mFdForLogging, this));
     return rv;
   }
   rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HPKP,
                         uri,
                         mProviderFlags,
                         mInfoObject->GetOriginAttributes(),
                         nullptr,
+                        nullptr,
                         &hasPinningInformation);
   if (NS_FAILED(rv)) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
             ("[%p][%p] checking for HPKP failed", mFdForLogging, this));
     return rv;
   }
 
   overrideAllowed = !strictTransportSecurityEnabled && !hasPinningInformation;
--- a/security/manager/ssl/nsISiteSecurityService.idl
+++ b/security/manager/ssl/nsISiteSecurityService.idl
@@ -87,16 +87,26 @@ interface nsISiteSecurityService : nsISu
     const uint32_t ERROR_INVALID_PIN = 9;
     const uint32_t ERROR_MULTIPLE_REPORT_URIS = 10;
     const uint32_t ERROR_PINSET_DOES_NOT_MATCH_CHAIN = 11;
     const uint32_t ERROR_NO_BACKUP_PIN = 12;
     const uint32_t ERROR_COULD_NOT_SAVE_STATE = 13;
     const uint32_t ERROR_ROOT_NOT_BUILT_IN = 14;
 
     /**
+     * nsISiteSecurityService::IsSecureURI can optionally return a flag
+     * indicating the source of the HSTS cache entry, if it comes from the
+     * preload list, was seen naturally, or is a result of HSTS priming.
+     */
+    const uint32_t SOURCE_UNKNOWN         = 0;
+    const uint32_t SOURCE_PRELOAD_LIST    = 1;
+    const uint32_t SOURCE_ORGANIC_REQUEST = 2;
+    const uint32_t SOURCE_HSTS_PRIMING    = 3;
+
+    /**
      * Parses a given HTTP header and records the results internally.
      * Currently two header types are supported: HSTS (aka STS) and HPKP
      * The format of the HSTS header is defined by the HSTS specification:
      * https://tools.ietf.org/html/rfc6797
      * and allows a host to specify that future HTTP requests should be
      * upgraded to HTTPS.
      * The format of the HPKP header is defined by the HPKP specification:
      * https://tools.ietf.org/html/rfc7469
@@ -109,43 +119,47 @@ interface nsISiteSecurityService : nsISu
      * @param aSSLStatus the SSLStatus of the current channel.
      * @param aFlags  options for this request as defined in nsISocketProvider:
      *                  NO_PERMANENT_STORAGE
      * @param aOriginAttributes the origin attributes that isolate this origin,
      *                          (note that this implementation does not isolate
      *                          by userContextId because of the risk of man-in-
      *                          the-middle attacks before trust-on-second-use
      *                          happens).
+     * @param aSource the source of the header, whether it was from the preload
+     *                list, an organic header, or HSTS priming, or unknown.
      * @param aMaxAge the parsed max-age directive of the header.
      * @param aIncludeSubdomains the parsed includeSubdomains directive.
      * @param aFailureResult a more specific failure result if NS_ERROR_FAILURE
                              was returned.
      * @return NS_OK            if it succeeds
      *         NS_ERROR_FAILURE if it can't be parsed
      *         NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
      *                          if there are unrecognized tokens in the header.
      */
     [binaryname(ProcessHeader), noscript, must_use]
     void processHeaderNative(in uint32_t aType,
                              in nsIURI aSourceURI,
                              in ACString aHeader,
                              in nsISSLStatus aSSLStatus,
                              in uint32_t aFlags,
+                             in uint32_t aSource,
                              in const_OriginAttributesRef aOriginAttributes,
                              [optional] out unsigned long long aMaxAge,
                              [optional] out boolean aIncludeSubdomains,
                              [optional] out uint32_t aFailureResult);
 
     [binaryname(ProcessHeaderScriptable), implicit_jscontext, optional_argc,
      must_use]
     void processHeader(in uint32_t aType,
                        in nsIURI aSourceURI,
                        in ACString aHeader,
                        in nsISSLStatus aSSLStatus,
                        in uint32_t aFlags,
+                       in uint32_t aSource,
                        [optional] in jsval aOriginAttributes,
                        [optional] out unsigned long long aMaxAge,
                        [optional] out boolean aIncludeSubdomains,
                        [optional] out uint32_t aFailureResult);
 
     /**
      * Given a header type, removes state relating to that header of a host,
      * including the includeSubdomains state that would affect subdomains.
@@ -180,30 +194,35 @@ interface nsISiteSecurityService : nsISu
      * @param aURI the URI to query for STS state.
      * @param aFlags  options for this request as defined in nsISocketProvider:
      *                  NO_PERMANENT_STORAGE
      * @param aOriginAttributes the origin attributes that isolate this origin,
      *                          (note that this implementation does not isolate
      *                          by userContextId because of the risk of man-in-
      *                          the-middle attacks before trust-on-second-use
      *                          happens).
-     * @param aCached true if we have cached information regarding whether or not
-     *                  the host is HSTS, false otherwise.
+     * @param aCached true if we have cached information about this host, even
+     *                if the security state is false.
+     * @param aSource the source of the HSTS entry. One of SOURCE_PRELOAD_LIST,
+     *                SOURCE_ORGANIC_REQUEST, SOURCE_HSTS_PRIMING, or
+     *                SOURCE_UNKNOWN. Not implemented for HPKP.
      */
     [binaryname(IsSecureURI), noscript, must_use]
     boolean isSecureURINative(in uint32_t aType, in nsIURI aURI,
                               in uint32_t aFlags,
                               in const_OriginAttributesRef aOriginAttributes,
-                              [optional] out boolean aCached);
+                              [optional] out boolean aCached,
+                              [optional] out uint32_t aSource);
 
     [binaryname(IsSecureURIScriptable), implicit_jscontext, optional_argc,
      must_use]
     boolean isSecureURI(in uint32_t aType, in nsIURI aURI, in uint32_t aFlags,
                         [optional] in jsval aOriginAttributes,
-                        [optional] out boolean aCached);
+                        [optional] out boolean aCached,
+                        [optional] out uint32_t aSource);
 
     /**
      * Removes all non-preloaded security state by resetting to factory-original
      * settings.
      */
     [must_use]
     void clearAll();
 
--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -111,16 +111,38 @@ public:
         break;
       default:
         return false;
     }
 
     return true;
   }
 
+  MOZ_MUST_USE bool
+  ReadSource(/*out*/ SecurityPropertySource& source)
+  {
+    uint32_t rawValue;
+    if (!ReadInteger(&rawValue)) {
+      return false;
+    }
+
+    source = static_cast<SecurityPropertySource>(rawValue);
+    switch (source) {
+      case SourceUnknown:
+      case SourcePreload:
+      case SourceOrganic:
+      case SourceHSTSPriming:
+        break;
+      default:
+        return false;
+    }
+
+    return true;
+  }
+
   // Note: Ideally, this method would be able to read SHA256 strings without
   // reading all the way to EOF. Unfortunately, if a token starts with digits
   // mozilla::Tokenizer will by default not consider the digits part of the
   // string. This can be worked around by making mozilla::Tokenizer consider
   // digit characters as "word" characters as well, but this can't be changed at
   // run time, meaning parsing digits as a number will fail.
   MOZ_MUST_USE bool
   ReadUntilEOFAsSHA256Keys(/*out*/ nsTArray<nsCString>& keys)
@@ -148,19 +170,21 @@ public:
   }
 };
 
 // Parses a state string like "1500918564034,1,1" into its constituent parts.
 bool
 ParseHSTSState(const nsCString& stateString,
        /*out*/ PRTime& expireTime,
        /*out*/ SecurityPropertyState& state,
-       /*out*/ bool& includeSubdomains)
+       /*out*/ bool& includeSubdomains,
+       /*out*/ SecurityPropertySource& source)
 {
   SSSTokenizer tokenizer(stateString);
+  SSSLOG(("Parsing state from %s", stateString.get()));
 
   if (!tokenizer.ReadInteger(&expireTime)) {
     return false;
   }
 
   if (!tokenizer.CheckChar(',')) {
     return false;
   }
@@ -172,63 +196,76 @@ ParseHSTSState(const nsCString& stateStr
   if (!tokenizer.CheckChar(',')) {
     return false;
   }
 
   if (!tokenizer.ReadBool(includeSubdomains)) {
     return false;
   }
 
+  source = SourceUnknown;
+  if (tokenizer.CheckChar(',')) {
+    if (!tokenizer.ReadSource(source)) {
+      return false;
+    }
+  }
+
   return tokenizer.CheckEOF();
 }
 
 } // namespace
 
 SiteHSTSState::SiteHSTSState(const nsCString& aHost,
                              const OriginAttributes& aOriginAttributes,
                              const nsCString& aStateString)
   : mHostname(aHost)
   , mOriginAttributes(aOriginAttributes)
   , mHSTSExpireTime(0)
   , mHSTSState(SecurityPropertyUnset)
   , mHSTSIncludeSubdomains(false)
+  , mHSTSSource(SourceUnknown)
 {
   bool valid = ParseHSTSState(aStateString, mHSTSExpireTime, mHSTSState,
-                              mHSTSIncludeSubdomains);
+                              mHSTSIncludeSubdomains, mHSTSSource);
   if (!valid) {
     SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get()));
     mHSTSExpireTime = 0;
     mHSTSState = SecurityPropertyUnset;
     mHSTSIncludeSubdomains = false;
+    mHSTSSource = SourceUnknown;
   }
 }
 
 SiteHSTSState::SiteHSTSState(const nsCString& aHost,
                              const OriginAttributes& aOriginAttributes,
                              PRTime aHSTSExpireTime,
                              SecurityPropertyState aHSTSState,
-                             bool aHSTSIncludeSubdomains)
+                             bool aHSTSIncludeSubdomains,
+                             SecurityPropertySource aSource)
 
   : mHostname(aHost)
   , mOriginAttributes(aOriginAttributes)
   , mHSTSExpireTime(aHSTSExpireTime)
   , mHSTSState(aHSTSState)
   , mHSTSIncludeSubdomains(aHSTSIncludeSubdomains)
+  , mHSTSSource(aSource)
 {
 }
 
 void
 SiteHSTSState::ToString(nsCString& aString)
 {
   aString.Truncate();
   aString.AppendInt(mHSTSExpireTime);
   aString.Append(',');
   aString.AppendInt(mHSTSState);
   aString.Append(',');
   aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains));
+  aString.Append(',');
+  aString.AppendInt(mHSTSSource);
 }
 
 NS_IMETHODIMP
 SiteHSTSState::GetHostname(nsACString& aHostname)
 {
   aHostname = mHostname;
   return NS_OK;
 }
@@ -576,72 +613,84 @@ ExpireTimeFromMaxAge(uint64_t maxAge)
 
 nsresult
 nsSiteSecurityService::SetHSTSState(uint32_t aType,
                                     const char* aHost,
                                     int64_t maxage,
                                     bool includeSubdomains,
                                     uint32_t flags,
                                     SecurityPropertyState aHSTSState,
-                                    bool aIsPreload,
+                                    SecurityPropertySource aSource,
                                     const OriginAttributes& aOriginAttributes)
 {
   nsAutoCString hostname(aHost);
+  bool isPreload = (aSource == SourcePreload);
   // If max-age is zero, that's an indication to immediately remove the
   // security state, so here's a shortcut.
   if (!maxage) {
-    return RemoveStateInternal(aType, hostname, flags, aIsPreload,
+    return RemoveStateInternal(aType, hostname, flags, isPreload,
                                aOriginAttributes);
   }
 
   MOZ_ASSERT((aHSTSState == SecurityPropertySet ||
               aHSTSState == SecurityPropertyNegative),
       "HSTS State must be SecurityPropertySet or SecurityPropertyNegative");
-  if (aIsPreload && aOriginAttributes != OriginAttributes()) {
+  if (isPreload && aOriginAttributes != OriginAttributes()) {
     return NS_ERROR_INVALID_ARG;
   }
 
   int64_t expiretime = ExpireTimeFromMaxAge(maxage);
   RefPtr<SiteHSTSState> siteState = new SiteHSTSState(
-    hostname, aOriginAttributes, expiretime, aHSTSState, includeSubdomains);
+    hostname, aOriginAttributes, expiretime, aHSTSState, includeSubdomains,
+    aSource);
   nsAutoCString stateString;
   siteState->ToString(stateString);
   SSSLOG(("SSS: setting state for %s", hostname.get()));
   bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
   nsAutoCString storageKey;
   SetStorageKey(hostname, aType, aOriginAttributes, storageKey);
   nsresult rv;
-  if (aIsPreload) {
+  if (isPreload) {
     SSSLOG(("SSS: storing entry for %s in dynamic preloads", hostname.get()));
     rv = mPreloadStateStorage->Put(storageKey, stateString,
                                    mozilla::DataStorage_Persistent);
   } else {
     SSSLOG(("SSS: storing HSTS site entry for %s", hostname.get()));
+    nsCString value = mSiteStateStorage->Get(storageKey, storageType);
+    RefPtr<SiteHSTSState> curSiteState =
+      new SiteHSTSState(hostname, aOriginAttributes, value);
+    if (curSiteState->mHSTSState != SecurityPropertyUnset &&
+        curSiteState->mHSTSSource != SourceUnknown) {
+      // don't override the source
+      siteState->mHSTSSource = curSiteState->mHSTSSource;
+      siteState->ToString(stateString);
+    }
     rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::CacheNegativeHSTSResult(
   nsIURI* aSourceURI,
   uint64_t aMaxAge,
   const OriginAttributes& aOriginAttributes)
 {
   nsAutoCString hostname;
   nsresult rv = GetHost(aSourceURI, hostname);
   NS_ENSURE_SUCCESS(rv, rv);
+  // SecurityPropertyNegative results only come from HSTS priming
   return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, hostname.get(),
-                      aMaxAge, false, 0, SecurityPropertyNegative, false,
-                      aOriginAttributes);
+                      aMaxAge, false, 0, SecurityPropertyNegative,
+                      SourceHSTSPriming, aOriginAttributes);
 }
 
 nsresult
 nsSiteSecurityService::RemoveStateInternal(
   uint32_t aType, nsIURI* aURI, uint32_t aFlags,
   const OriginAttributes& aOriginAttributes)
 {
   nsAutoCString hostname;
@@ -680,17 +729,18 @@ nsSiteSecurityService::RemoveStateIntern
   nsCString value = mPreloadStateStorage->Get(storageKey,
                                               mozilla::DataStorage_Persistent);
   RefPtr<SiteHSTSState> dynamicState =
     new SiteHSTSState(aHost, aOriginAttributes, value);
   if (GetPreloadListEntry(aHost.get()) ||
       dynamicState->mHSTSState != SecurityPropertyUnset) {
     SSSLOG(("SSS: storing knockout entry for %s", aHost.get()));
     RefPtr<SiteHSTSState> siteState = new SiteHSTSState(
-      aHost, aOriginAttributes, 0, SecurityPropertyKnockout, false);
+      aHost, aOriginAttributes, 0, SecurityPropertyKnockout, false,
+      SourceUnknown);
     nsAutoCString stateString;
     siteState->ToString(stateString);
     nsresult rv;
     if (aIsPreload) {
       rv = mPreloadStateStorage->Put(storageKey, stateString,
                                      mozilla::DataStorage_Persistent);
     } else {
       rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
@@ -735,41 +785,43 @@ HostIsIPAddress(const nsCString& hostnam
 
 NS_IMETHODIMP
 nsSiteSecurityService::ProcessHeaderScriptable(
   uint32_t aType,
   nsIURI* aSourceURI,
   const nsACString& aHeader,
   nsISSLStatus* aSSLStatus,
   uint32_t aFlags,
+  uint32_t aSource,
   JS::HandleValue aOriginAttributes,
   uint64_t* aMaxAge,
   bool* aIncludeSubdomains,
   uint32_t* aFailureResult,
   JSContext* aCx,
   uint8_t aArgc)
 {
   OriginAttributes originAttributes;
   if (aArgc > 0) {
     if (!aOriginAttributes.isObject() ||
         !originAttributes.Init(aCx, aOriginAttributes)) {
       return NS_ERROR_INVALID_ARG;
     }
   }
   return ProcessHeader(aType, aSourceURI, aHeader, aSSLStatus, aFlags,
-                       originAttributes, aMaxAge, aIncludeSubdomains,
+                       aSource, originAttributes, aMaxAge, aIncludeSubdomains,
                        aFailureResult);
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::ProcessHeader(uint32_t aType,
                                      nsIURI* aSourceURI,
                                      const nsACString& aHeader,
                                      nsISSLStatus* aSSLStatus,
                                      uint32_t aFlags,
+                                     uint32_t aHeaderSource,
                                      const OriginAttributes& aOriginAttributes,
                                      uint64_t* aMaxAge,
                                      bool* aIncludeSubdomains,
                                      uint32_t* aFailureResult)
 {
   // Child processes are not allowed direct access to this.
   if (!XRE_IsParentProcess()) {
     MOZ_CRASH("Child process: no direct access to "
@@ -777,30 +829,41 @@ nsSiteSecurityService::ProcessHeader(uin
   }
 
   if (aFailureResult) {
     *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
   }
   NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
                  aType == nsISiteSecurityService::HEADER_HPKP,
                  NS_ERROR_NOT_IMPLEMENTED);
+  SecurityPropertySource source = static_cast<SecurityPropertySource>(aHeaderSource);
+  switch (source) {
+    case SourceUnknown:
+    case SourcePreload:
+    case SourceOrganic:
+    case SourceHSTSPriming:
+      break;
+    default:
+      return NS_ERROR_INVALID_ARG;
+  }
 
   NS_ENSURE_ARG(aSSLStatus);
   return ProcessHeaderInternal(aType, aSourceURI, PromiseFlatCString(aHeader),
-                               aSSLStatus, aFlags, aOriginAttributes, aMaxAge,
-                               aIncludeSubdomains, aFailureResult);
+                               aSSLStatus, aFlags, source, aOriginAttributes,
+                               aMaxAge, aIncludeSubdomains, aFailureResult);
 }
 
 nsresult
 nsSiteSecurityService::ProcessHeaderInternal(
   uint32_t aType,
   nsIURI* aSourceURI,
   const nsCString& aHeader,
   nsISSLStatus* aSSLStatus,
   uint32_t aFlags,
+  SecurityPropertySource aSource,
   const OriginAttributes& aOriginAttributes,
   uint64_t* aMaxAge,
   bool* aIncludeSubdomains,
   uint32_t* aFailureResult)
 {
   if (aFailureResult) {
     *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
   }
@@ -846,18 +909,19 @@ nsSiteSecurityService::ProcessHeaderInte
   NS_ENSURE_SUCCESS(rv, rv);
   if (HostIsIPAddress(host)) {
     /* Don't process headers if a site is accessed by IP address. */
     return NS_OK;
   }
 
   switch (aType) {
     case nsISiteSecurityService::HEADER_HSTS:
-      rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aOriginAttributes, aMaxAge,
-                            aIncludeSubdomains, aFailureResult);
+      rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aSource,
+                            aOriginAttributes, aMaxAge, aIncludeSubdomains,
+                            aFailureResult);
       break;
     case nsISiteSecurityService::HEADER_HPKP:
       rv = ProcessPKPHeader(aSourceURI, aHeader, aSSLStatus, aFlags,
                             aOriginAttributes, aMaxAge, aIncludeSubdomains,
                             aFailureResult);
       break;
     default:
       MOZ_CRASH("unexpected header type");
@@ -1182,16 +1246,17 @@ nsSiteSecurityService::ProcessPKPHeader(
            : NS_OK;
 }
 
 nsresult
 nsSiteSecurityService::ProcessSTSHeader(
   nsIURI* aSourceURI,
   const nsCString& aHeader,
   uint32_t aFlags,
+  SecurityPropertySource aSource,
   const OriginAttributes& aOriginAttributes,
   uint64_t* aMaxAge,
   bool* aIncludeSubdomains,
   uint32_t* aFailureResult)
 {
   if (aFailureResult) {
     *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
   }
@@ -1225,17 +1290,17 @@ nsSiteSecurityService::ProcessSTSHeader(
   }
 
   nsAutoCString hostname;
   nsresult rv = GetHost(aSourceURI, hostname);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // record the successfully parsed header data.
   rv = SetHSTSState(aType, hostname.get(), maxAge, foundIncludeSubdomains,
-                    aFlags, SecurityPropertySet, false, aOriginAttributes);
+                    aFlags, SecurityPropertySet, aSource, aOriginAttributes);
   if (NS_FAILED(rv)) {
     SSSLOG(("SSS: failed to set STS state"));
     if (aFailureResult) {
       *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
     }
     return rv;
   }
 
@@ -1251,34 +1316,36 @@ nsSiteSecurityService::ProcessSTSHeader(
            ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
            : NS_OK;
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::IsSecureURIScriptable(uint32_t aType, nsIURI* aURI,
                                              uint32_t aFlags,
                                              JS::HandleValue aOriginAttributes,
-                                             bool* aCached, JSContext* aCx,
+                                             bool* aCached,
+                                             uint32_t* aSource, JSContext* aCx,
                                              uint8_t aArgc,  bool* aResult)
 {
   OriginAttributes originAttributes;
   if (aArgc > 0) {
     if (!aOriginAttributes.isObject() ||
         !originAttributes.Init(aCx, aOriginAttributes)) {
       return NS_ERROR_INVALID_ARG;
     }
   }
-  return IsSecureURI(aType, aURI, aFlags, originAttributes, aCached, aResult);
+  return IsSecureURI(aType, aURI, aFlags, originAttributes, aCached, aSource, aResult);
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
                                    uint32_t aFlags,
                                    const OriginAttributes& aOriginAttributes,
-                                   bool* aCached, bool* aResult)
+                                   bool* aCached,
+                                   uint32_t* aSource, bool* aResult)
 {
    // Child processes are not allowed direct access to this.
    if (!XRE_IsParentProcess() && aType != nsISiteSecurityService::HEADER_HSTS) {
      MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::IsSecureURI for non-HSTS entries");
    }
 
   NS_ENSURE_ARG(aURI);
   NS_ENSURE_ARG(aResult);
@@ -1292,18 +1359,20 @@ nsSiteSecurityService::IsSecureURI(uint3
   nsresult rv = GetHost(aURI, hostname);
   NS_ENSURE_SUCCESS(rv, rv);
   /* An IP address never qualifies as a secure URI. */
   if (HostIsIPAddress(hostname)) {
     *aResult = false;
     return NS_OK;
   }
 
+  SecurityPropertySource* source = BitwiseCast<SecurityPropertySource*>(aSource);
+
   return IsSecureHost(aType, hostname, aFlags, aOriginAttributes, aCached,
-                      aResult);
+                      source, aResult);
 }
 
 int STSPreloadCompare(const void *key, const void *entry)
 {
   const char *keyStr = (const char *)key;
   const nsSTSPreload *preloadEntry = (const nsSTSPreload *)entry;
   return strcmp(keyStr, &kSTSHostTable[preloadEntry->mHostIndex]);
 }
@@ -1330,18 +1399,25 @@ nsSiteSecurityService::GetPreloadListEnt
 // so, what that state is). The return value says whether or not we know
 // anything about this host (true if the host has an HSTS entry). aHost is
 // the host which we wish to deteming HSTS information on,
 // aRequireIncludeSubdomains specifies whether we require includeSubdomains
 // to be set on the entry (with the other parameters being as per IsSecureHost).
 bool
 nsSiteSecurityService::HostHasHSTSEntry(
   const nsAutoCString& aHost, bool aRequireIncludeSubdomains, uint32_t aFlags,
-  const OriginAttributes& aOriginAttributes, bool* aResult, bool* aCached)
+  const OriginAttributes& aOriginAttributes, bool* aResult, bool* aCached,
+  SecurityPropertySource* aSource)
 {
+  if (aSource) {
+    *aSource = SourceUnknown;
+  }
+  if (aCached) {
+    *aCached = false;
+  }
   // First we check for an entry in site security storage. If that entry exists,
   // we don't want to check in the preload lists. We only want to use the
   // stored value if it is not a knockout entry, however.
   // Additionally, if it is a knockout entry, we want to stop looking for data
   // on the host, because the knockout entry indicates "we have no information
   // regarding the security status of this host".
   bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
@@ -1365,23 +1441,30 @@ nsSiteSecurityService::HostHasHSTSEntry(
       if (siteState->mHSTSState == SecurityPropertySet) {
         *aResult = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
                                              : true;
         if (aCached) {
           // Only set cached if this includes subdomains
           *aCached = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
                                                : true;
         }
+        if (aSource) {
+          *aSource = siteState->mHSTSSource;
+        }
         return true;
       } else if (siteState->mHSTSState == SecurityPropertyNegative) {
         *aResult = false;
         if (aCached) {
           // if it's negative, it is always cached
+          SSSLOG(("Marking HSTS as as cached (SecurityPropertyNegative)"));
           *aCached = true;
         }
+        if (aSource) {
+          *aSource = siteState->mHSTSSource;
+        }
         return true;
       }
     }
 
     if (expired) {
       SSSLOG(("Entry %s is expired - checking for preload state", aHost.get()));
       // If the entry is expired and is not in either the static or dynamic
       // preload lists, we can remove it.
@@ -1414,23 +1497,29 @@ nsSiteSecurityService::HostHasHSTSEntry(
       if (dynamicState->mHSTSState == SecurityPropertySet) {
         *aResult = aRequireIncludeSubdomains ? dynamicState->mHSTSIncludeSubdomains
                                              : true;
         if (aCached) {
           // Only set cached if this includes subdomains
           *aCached = aRequireIncludeSubdomains ? dynamicState->mHSTSIncludeSubdomains
                                                : true;
         }
+        if (aSource) {
+          *aSource = dynamicState->mHSTSSource;
+        }
         return true;
       } else if (dynamicState->mHSTSState == SecurityPropertyNegative) {
         *aResult = false;
         if (aCached) {
           // if it's negative, it is always cached
           *aCached = true;
         }
+        if (aSource) {
+          *aSource = dynamicState->mHSTSSource;
+        }
         return true;
       }
     } else {
       // if a dynamic preload has expired and is not in the static preload
       // list, we can remove it.
       if (!GetPreloadListEntry(aHost.get())) {
         mPreloadStateStorage->Remove(preloadKey,
                                      mozilla::DataStorage_Persistent);
@@ -1445,49 +1534,51 @@ nsSiteSecurityService::HostHasHSTSEntry(
   if (siteState->mHSTSState == SecurityPropertyUnset &&
       dynamicState->mHSTSState == SecurityPropertyUnset &&
       (preload = GetPreloadListEntry(aHost.get())) != nullptr) {
     SSSLOG(("%s is a preloaded HSTS host", aHost.get()));
     *aResult = aRequireIncludeSubdomains ? preload->mIncludeSubdomains
                                          : true;
     if (aCached) {
       // Only set cached if this includes subdomains
-      *aCached = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
+      *aCached = aRequireIncludeSubdomains ? preload->mIncludeSubdomains
                                            : true;
     }
+    if (aSource) {
+      *aSource = SourcePreload;
+    }
     return true;
   }
 
   return false;
 }
 
 nsresult
 nsSiteSecurityService::IsSecureHost(uint32_t aType, const nsACString& aHost,
                                     uint32_t aFlags,
                                     const OriginAttributes& aOriginAttributes,
-                                    bool* aCached, bool* aResult)
+                                    bool* aCached,
+                                    SecurityPropertySource* aSource,
+                                    bool* aResult)
 {
   // Child processes are not allowed direct access to this.
   if (!XRE_IsParentProcess() && aType != nsISiteSecurityService::HEADER_HSTS) {
     MOZ_CRASH("Child process: no direct access to "
               "nsISiteSecurityService::IsSecureHost for non-HSTS entries");
   }
 
   NS_ENSURE_ARG(aResult);
 
   // Only HSTS and HPKP are supported at the moment.
   NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
                  aType == nsISiteSecurityService::HEADER_HPKP,
                  NS_ERROR_NOT_IMPLEMENTED);
 
   // set default in case if we can't find any STS information
   *aResult = false;
-  if (aCached) {
-    *aCached = false;
-  }
 
   /* An IP address never qualifies as a secure URI. */
   const nsCString& flatHost = PromiseFlatCString(aHost);
   if (HostIsIPAddress(flatHost)) {
     return NS_OK;
   }
 
   if (aType == nsISiteSecurityService::HEADER_HPKP) {
@@ -1510,21 +1601,25 @@ nsSiteSecurityService::IsSecureHost(uint
   // Holepunch chart.apis.google.com and subdomains.
   nsAutoCString host(
     PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
   if (host.EqualsLiteral("chart.apis.google.com") ||
       StringEndsWith(host, NS_LITERAL_CSTRING(".chart.apis.google.com"))) {
     if (aCached) {
       *aCached = true;
     }
+    if (aSource) {
+      *aSource = SourcePreload;
+    }
     return NS_OK;
   }
 
   // First check the exact host.
-  if (HostHasHSTSEntry(host, false, aFlags, aOriginAttributes, aResult, aCached)) {
+  if (HostHasHSTSEntry(host, false, aFlags, aOriginAttributes, aResult,
+                       aCached, aSource)) {
     return NS_OK;
   }
 
 
   SSSLOG(("no HSTS data for %s found, walking up domain", host.get()));
   const char *subdomain;
 
   uint32_t offset = 0;
@@ -1540,17 +1635,17 @@ nsSiteSecurityService::IsSecureHost(uint
     }
 
     // Do the same thing as with the exact host except now we're looking at
     // ancestor domains of the original host and, therefore, we have to require
     // that the entry includes subdomains.
     nsAutoCString subdomainString(subdomain);
 
     if (HostHasHSTSEntry(subdomainString, true, aFlags, aOriginAttributes, aResult,
-                         aCached)) {
+                         aCached, aSource)) {
       break;
     }
 
     SSSLOG(("no HSTS data for %s found, walking up domain", subdomain));
   }
 
   // Use whatever we ended up with, which defaults to false.
   return NS_OK;
@@ -1713,18 +1808,18 @@ nsSiteSecurityService::SetHSTSPreload(co
   NS_ENSURE_ARG_POINTER(aResult);
 
   SSSLOG(("Top of SetHSTSPreload"));
 
   const nsCString& flatHost = PromiseFlatCString(aHost);
   nsAutoCString host(
     PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
   return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, host.get(), aExpires,
-                      aIncludeSubdomains, 0, SecurityPropertySet, true,
-                      OriginAttributes());
+                      aIncludeSubdomains, 0, SecurityPropertySet,
+                      SourcePreload, OriginAttributes());
 }
 
 nsresult
 nsSiteSecurityService::SetHPKPState(const char* aHost, SiteHPKPState& entry,
                                     uint32_t aFlags, bool aIsPreload,
                                     const OriginAttributes& aOriginAttributes)
 {
   if (aIsPreload && aOriginAttributes != OriginAttributes()) {
--- a/security/manager/ssl/nsSiteSecurityService.h
+++ b/security/manager/ssl/nsSiteSecurityService.h
@@ -37,16 +37,23 @@ using mozilla::OriginAttributes;
  */
 enum SecurityPropertyState {
   SecurityPropertyUnset = nsISiteSecurityState::SECURITY_PROPERTY_UNSET,
   SecurityPropertySet = nsISiteSecurityState::SECURITY_PROPERTY_SET,
   SecurityPropertyKnockout = nsISiteSecurityState::SECURITY_PROPERTY_KNOCKOUT,
   SecurityPropertyNegative = nsISiteSecurityState::SECURITY_PROPERTY_NEGATIVE,
 };
 
+enum SecurityPropertySource {
+  SourceUnknown = nsISiteSecurityService::SOURCE_UNKNOWN,
+  SourcePreload = nsISiteSecurityService::SOURCE_PRELOAD_LIST,
+  SourceOrganic = nsISiteSecurityService::SOURCE_ORGANIC_REQUEST,
+  SourceHSTSPriming = nsISiteSecurityService::SOURCE_HSTS_PRIMING,
+};
+
 /**
  * SiteHPKPState: A utility class that encodes/decodes a string describing
  * the public key pins of a site.
  * HPKP state consists of:
  *  - Hostname (nsCString)
  *  - Origin attributes (OriginAttributes)
  *  - Expiry time (PRTime (aka int64_t) in milliseconds)
  *  - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
@@ -109,23 +116,25 @@ public:
   NS_DECL_NSISITESECURITYSTATE
 
   SiteHSTSState(const nsCString& aHost,
                 const OriginAttributes& aOriginAttributes,
                 const nsCString& aStateString);
   SiteHSTSState(const nsCString& aHost,
                 const OriginAttributes& aOriginAttributes,
                 PRTime aHSTSExpireTime, SecurityPropertyState aHSTSState,
-                bool aHSTSIncludeSubdomains);
+                bool aHSTSIncludeSubdomains,
+                SecurityPropertySource aSource);
 
   nsCString mHostname;
   OriginAttributes mOriginAttributes;
   PRTime mHSTSExpireTime;
   SecurityPropertyState mHSTSState;
   bool mHSTSIncludeSubdomains;
+  SecurityPropertySource mHSTSSource;
 
   bool IsExpired(uint32_t aType)
   {
     // If mHSTSExpireTime is 0, this entry never expires (this is the case for
     // knockout entries).
     if (mHSTSExpireTime == 0) {
       return false;
     }
@@ -159,27 +168,30 @@ public:
 
 protected:
   virtual ~nsSiteSecurityService();
 
 private:
   nsresult GetHost(nsIURI *aURI, nsACString &aResult);
   nsresult SetHSTSState(uint32_t aType, const char* aHost, int64_t maxage,
                         bool includeSubdomains, uint32_t flags,
-                        SecurityPropertyState aHSTSState, bool aIsPreload,
+                        SecurityPropertyState aHSTSState,
+                        SecurityPropertySource aSource,
                         const OriginAttributes& aOriginAttributes);
   nsresult ProcessHeaderInternal(uint32_t aType, nsIURI* aSourceURI,
                                  const nsCString& aHeader,
                                  nsISSLStatus* aSSLStatus,
                                  uint32_t aFlags,
+                                 SecurityPropertySource aSource,
                                  const OriginAttributes& aOriginAttributes,
                                  uint64_t* aMaxAge, bool* aIncludeSubdomains,
                                  uint32_t* aFailureResult);
   nsresult ProcessSTSHeader(nsIURI* aSourceURI, const nsCString& aHeader,
                             uint32_t flags,
+                            SecurityPropertySource aSource,
                             const OriginAttributes& aOriginAttributes,
                             uint64_t* aMaxAge, bool* aIncludeSubdomains,
                             uint32_t* aFailureResult);
   nsresult ProcessPKPHeader(nsIURI* aSourceURI, const nsCString& aHeader,
                             nsISSLStatus* aSSLStatus, uint32_t flags,
                             const OriginAttributes& aOriginAttributes,
                             uint64_t* aMaxAge, bool* aIncludeSubdomains,
                             uint32_t* aFailureResult);
@@ -189,22 +201,24 @@ private:
   nsresult RemoveStateInternal(uint32_t aType, nsIURI* aURI, uint32_t aFlags,
                                const OriginAttributes& aOriginAttributes);
   nsresult RemoveStateInternal(uint32_t aType, const nsAutoCString& aHost,
                                uint32_t aFlags, bool aIsPreload,
                                const OriginAttributes& aOriginAttributes);
   bool HostHasHSTSEntry(const nsAutoCString& aHost,
                         bool aRequireIncludeSubdomains, uint32_t aFlags,
                         const OriginAttributes& aOriginAttributes,
-                        bool* aResult, bool* aCached);
+                        bool* aResult, bool* aCached,
+                        SecurityPropertySource* aSource);
   const nsSTSPreload *GetPreloadListEntry(const char *aHost);
   nsresult IsSecureHost(uint32_t aType, const nsACString& aHost,
                         uint32_t aFlags,
                         const OriginAttributes& aOriginAttributes,
-                        bool* aCached, bool* aResult);
+                        bool* aCached, SecurityPropertySource* aSource,
+                        bool* aResult);
 
   uint64_t mMaxMaxAge;
   bool mUsePreloadList;
   int64_t mPreloadListTimeOffset;
   bool mProcessPKPHeadersFromNonBuiltInRoots;
   RefPtr<mozilla::DataStorage> mSiteStateStorage;
   RefPtr<mozilla::DataStorage> mPreloadStateStorage;
 };
--- a/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
+++ b/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
@@ -48,17 +48,18 @@ function test() {
     return aIsPrivateMode ? Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
   }
 
   function doTest(aIsPrivateMode, aWindow, aCallback) {
     aWindow.gBrowser.selectedBrowser.addEventListener("load", function() {
       let sslStatus = new FakeSSLStatus();
       uri = aWindow.Services.io.newURI("https://localhost/img.png");
       gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                               "max-age=1000", sslStatus, privacyFlags(aIsPrivateMode));
+                               "max-age=1000", sslStatus, privacyFlags(aIsPrivateMode),
+                               Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
       ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
                                 privacyFlags(aIsPrivateMode)),
                                 "checking sts host");
 
       aCallback();
     }, {capture: true, once: true});
 
     aWindow.gBrowser.selectedBrowser.loadURI(testURI);
--- a/security/manager/ssl/tests/unit/test_forget_about_site_security_headers.js
+++ b/security/manager/ssl/tests/unit/test_forget_about_site_security_headers.js
@@ -45,19 +45,21 @@ var sslStatus = new FakeSSLStatus(constr
   "test_pinning_dynamic/a.pinning2.example.com-pinningroot.pem"));
 
 // Test the normal case of processing HSTS and HPKP headers for
 // a.pinning2.example.com, using "Forget About Site" on a.pinning2.example.com,
 // and then checking that the platform doesn't consider a.pinning2.example.com
 // to be HSTS or HPKP any longer.
 add_task(function* () {
   sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, GOOD_MAX_AGE,
-                    sslStatus, 0);
+                    sslStatus, 0,
+                    Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
-                    GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0);
+                    GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0,
+                    Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
 
   Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
             "a.pinning2.example.com should be HSTS");
   Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0),
             "a.pinning2.example.com should be HPKP");
 
   yield ForgetAboutSite.removeDataFromDomain("a.pinning2.example.com");
 
@@ -68,30 +70,33 @@ add_task(function* () {
 });
 
 // Test the case of processing HSTS and HPKP headers for a.pinning2.example.com,
 // using "Forget About Site" on example.com, and then checking that the platform
 // doesn't consider the subdomain to be HSTS or HPKP any longer. Also test that
 // unrelated sites don't also get removed.
 add_task(function* () {
   sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, GOOD_MAX_AGE,
-                    sslStatus, 0);
+                    sslStatus, 0,
+                    Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
-                    GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0);
+                    GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0,
+                    Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
 
   Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
             "a.pinning2.example.com should be HSTS (subdomain case)");
   Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0),
             "a.pinning2.example.com should be HPKP (subdomain case)");
 
   // Add an unrelated site to HSTS.  Not HPKP because we have no valid keys for
   // example.org.
   let unrelatedURI = Services.io.newURI("https://example.org");
   sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, unrelatedURI,
-                    GOOD_MAX_AGE, sslStatus, 0);
+                    GOOD_MAX_AGE, sslStatus, 0,
+                    Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
                              unrelatedURI, 0), "example.org should be HSTS");
 
   yield ForgetAboutSite.removeDataFromDomain("example.com");
 
   Assert.ok(!sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
             "a.pinning2.example.com should not be HSTS now (subdomain case)");
   Assert.ok(!sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0),
@@ -114,31 +119,36 @@ add_task(function* () {
     { firstPartyDomain: "foo.com" },
     { userContextId: 1, firstPartyDomain: "foo.com" },
   ];
 
   let unrelatedURI = Services.io.newURI("https://example.org");
 
   for (let originAttributes of originAttributesList) {
     sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, GOOD_MAX_AGE,
-                      sslStatus, 0, originAttributes);
+                      sslStatus, 0,
+                      Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
+                      originAttributes);
     sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
                       GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0,
+                      Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
                       originAttributes);
 
     Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
                                0, originAttributes),
               "a.pinning2.example.com should be HSTS (originAttributes case)");
     Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
                                0, originAttributes),
               "a.pinning2.example.com should be HPKP (originAttributes case)");
 
     // Add an unrelated site to HSTS.  Not HPKP because we have no valid keys.
     sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, unrelatedURI,
-                      GOOD_MAX_AGE, sslStatus, 0, originAttributes);
+                      GOOD_MAX_AGE, sslStatus, 0,
+                      Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
+                      originAttributes);
     Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
                               unrelatedURI, 0, originAttributes),
               "example.org should be HSTS (originAttributes case)");
   }
 
   yield ForgetAboutSite.removeDataFromDomain("example.com");
 
   for (let originAttributes of originAttributesList) {
--- a/security/manager/ssl/tests/unit/test_ocsp_must_staple.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_must_staple.js
@@ -33,17 +33,18 @@ function add_tests() {
     let uri = Services.io.newURI("https://ocsp-stapling-must-staple-ee-with-must-staple-int.example.com");
     let keyHash = "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=";
     let backupKeyHash = "KHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN=";
     let header = `max-age=1000; pin-sha256="${keyHash}"; pin-sha256="${backupKeyHash}"`;
     let ssservice = Cc["@mozilla.org/ssservice;1"]
                       .getService(Ci.nsISiteSecurityService);
     let sslStatus = new FakeSSLStatus();
     sslStatus.serverCert = constructCertFromFile("ocsp_certs/must-staple-ee-with-must-staple-int.pem");
-    ssservice.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri, header, sslStatus, 0);
+    ssservice.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri, header, sslStatus, 0,
+                            Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
     ok(ssservice.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0),
        "ocsp-stapling-must-staple-ee-with-must-staple-int.example.com should have HPKP set");
 
     // Clear accumulated state.
     ssservice.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0);
     Services.prefs.clearUserPref("security.cert_pinning.process_headers_from_non_builtin_roots");
     Services.prefs.clearUserPref("security.cert_pinning.enforcement_level");
     run_next_test();
--- a/security/manager/ssl/tests/unit/test_ocsp_no_hsts_upgrade.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_no_hsts_upgrade.js
@@ -39,15 +39,16 @@ function run_test() {
 
   add_test(function () { ocspResponder.stop(run_next_test); });
 
   let SSService = Cc["@mozilla.org/ssservice;1"]
                     .getService(Ci.nsISiteSecurityService);
   let uri = Services.io.newURI("http://localhost");
   let sslStatus = new FakeSSLStatus();
   SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                          "max-age=10000", sslStatus, 0);
+                          "max-age=10000", sslStatus, 0,
+                          Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
      "Domain for the OCSP AIA URI should be considered a HSTS host, otherwise" +
      " we wouldn't be testing what we think we're testing");
 
   run_next_test();
 }
--- a/security/manager/ssl/tests/unit/test_pinning_header_parsing.js
+++ b/security/manager/ssl/tests/unit/test_pinning_header_parsing.js
@@ -23,17 +23,18 @@ function loadCert(cert_name, trust_strin
 }
 
 function checkFailParseInvalidPin(pinValue) {
   let sslStatus = new FakeSSLStatus(
                         certFromFile("a.pinning2.example.com-pinningroot"));
   let uri = Services.io.newURI("https://a.pinning2.example.com");
   throws(() => {
     gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
-                             pinValue, sslStatus, 0);
+                             pinValue, sslStatus, 0,
+                             Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   }, /NS_ERROR_FAILURE/, `Invalid pin "${pinValue}" should be rejected`);
 }
 
 function checkPassValidPin(pinValue, settingPin, expectedMaxAge) {
   let sslStatus = new FakeSSLStatus(
                         certFromFile("a.pinning2.example.com-pinningroot"));
   let uri = Services.io.newURI("https://a.pinning2.example.com");
   let maxAge = {};
@@ -41,21 +42,24 @@ function checkPassValidPin(pinValue, set
   // setup preconditions for the test, if setting ensure there is no previous
   // state, if removing ensure there is a valid pin in place.
   if (settingPin) {
     gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0);
   } else {
     // add a known valid pin!
     let validPinValue = "max-age=5000;" + VALID_PIN1 + BACKUP_PIN1;
     gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
-                             validPinValue, sslStatus, 0);
+                             validPinValue, sslStatus, 0,
+                             Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   }
   try {
     gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
-                             pinValue, sslStatus, 0, {}, maxAge);
+                             pinValue, sslStatus, 0,
+                             Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
+                             {}, maxAge);
     ok(true, "Valid pin should be accepted");
   } catch (e) {
     ok(false, "Valid pin should have been accepted");
   }
 
   // check that maxAge was processed correctly
   if (settingPin && expectedMaxAge) {
     ok(maxAge.value == expectedMaxAge, `max-age value should be ${expectedMaxAge}`);
--- a/security/manager/ssl/tests/unit/test_sss_enumerate.js
+++ b/security/manager/ssl/tests/unit/test_sss_enumerate.js
@@ -47,22 +47,24 @@ function insertEntries() {
       `test_pinning_dynamic/${testcase.hostname}-pinningroot.pem`));
     // MaxAge is in seconds.
     let maxAge = Math.round((testcase.expireTime - Date.now()) / 1000);
     let header = `max-age=${maxAge}`;
     if (testcase.includeSubdomains) {
       header += "; includeSubdomains";
     }
     sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, header,
-                      sslStatus, 0);
+                      sslStatus, 0,
+                      Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
     for (let key of KEY_HASHES) {
       header += `; pin-sha256="${key}"`;
     }
     sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri, header,
-                      sslStatus, 0);
+                      sslStatus, 0,
+                      Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   }
 }
 
 function getEntries(type) {
   let entryEnumerator = sss.enumerate(type);
   let entries = [];
   while (entryEnumerator.hasMoreElements()) {
     let entry = entryEnumerator.getNext();
--- a/security/manager/ssl/tests/unit/test_sss_eviction.js
+++ b/security/manager/ssl/tests/unit/test_sss_eviction.js
@@ -53,17 +53,18 @@ function do_state_read(aSubject, aTopic,
 
   ok(gSSService.isSecureURI(
        Ci.nsISiteSecurityService.HEADER_HSTS,
        Services.io.newURI("https://frequentlyused.example.com"), 0));
   let sslStatus = new FakeSSLStatus();
   for (let i = 0; i < 2000; i++) {
     let uri = Services.io.newURI("http://bad" + i + ".example.com");
     gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                            "max-age=1000", sslStatus, 0);
+                            "max-age=1000", sslStatus, 0,
+                            Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   }
   do_test_pending();
   Services.obs.addObserver(do_state_written, "data-storage-written");
   do_test_finished();
 }
 
 function run_test() {
   Services.prefs.setIntPref("test.datastorage.write_timer_ms", 100);
--- a/security/manager/ssl/tests/unit/test_sss_originAttributes.js
+++ b/security/manager/ssl/tests/unit/test_sss_originAttributes.js
@@ -48,17 +48,19 @@ function doTest(originAttributes1, origi
   sss.clearAll();
   for (let type of [Ci.nsISiteSecurityService.HEADER_HSTS,
                     Ci.nsISiteSecurityService.HEADER_HPKP]) {
     let header = GOOD_MAX_AGE;
     if (type == Ci.nsISiteSecurityService.HEADER_HPKP) {
       header += VALID_PIN + BACKUP_PIN;
     }
     // Set HSTS or HPKP for originAttributes1.
-    sss.processHeader(type, uri, header, sslStatus, 0, originAttributes1);
+    sss.processHeader(type, uri, header, sslStatus, 0,
+                      Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
+                      originAttributes1);
     ok(sss.isSecureURI(type, uri, 0, originAttributes1),
        "URI should be secure given original origin attributes");
     equal(sss.isSecureURI(type, uri, 0, originAttributes2), shouldShare,
           "URI should be secure given different origin attributes if and " +
           "only if shouldShare is true");
 
     if (!shouldShare) {
       // Remove originAttributes2 from the storage.
@@ -95,16 +97,17 @@ function testInvalidOriginAttributes(ori
                     Ci.nsISiteSecurityService.HEADER_HPKP]) {
     let header = GOOD_MAX_AGE;
     if (type == Ci.nsISiteSecurityService.HEADER_HPKP) {
       header += VALID_PIN + BACKUP_PIN;
     }
 
     let callbacks = [
       () => sss.processHeader(type, uri, header, sslStatus, 0,
+                              Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
                               originAttributes),
       () => sss.isSecureURI(type, uri, 0, originAttributes),
       () => sss.removeState(type, uri, 0, originAttributes),
     ];
 
     for (let callback of callbacks) {
       throws(callback, /NS_ERROR_ILLEGAL_VALUE/,
              "Should get an error with invalid origin attributes");
--- a/security/manager/ssl/tests/unit/test_sss_savestate.js
+++ b/security/manager/ssl/tests/unit/test_sss_savestate.js
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // The purpose of this test is to see that the site security service properly
 // writes its state file.
 
 const EXPECTED_ENTRIES = 6;
-const EXPECTED_HSTS_COLUMNS = 3;
+const EXPECTED_HSTS_COLUMNS = 4;
 const EXPECTED_HPKP_COLUMNS = 4;
 var gProfileDir = null;
 
 const NON_ISSUED_KEY_HASH = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
 
 // For reference, the format of the state file is a list of:
 // <domain name> <expiration time in milliseconds>,<sts status>,<includeSubdomains>
 // separated by newlines ('\n')
@@ -113,14 +113,15 @@ function run_test() {
     let uriIndex = i % uris.length;
     // vary max-age
     let maxAge = "max-age=" + (i * 1000);
      // alternate setting includeSubdomains
     let includeSubdomains = (i % 2 == 0 ? "; includeSubdomains" : "");
     let sslStatus = new FakeSSLStatus();
     SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS,
                             uris[uriIndex], maxAge + includeSubdomains,
-                            sslStatus, 0);
+                            sslStatus, 0,
+                            Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   }
 
   do_test_pending();
   Services.obs.addObserver(checkStateWritten, "data-storage-written");
 }
--- a/security/manager/ssl/tests/unit/test_sts_fqdn.js
+++ b/security/manager/ssl/tests/unit/test_sts_fqdn.js
@@ -12,17 +12,18 @@ function run_test() {
   let uri2 = Services.io.newURI("https://example.com..");
   ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
   ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri1, 0));
   // These cases are only relevant as long as bug 1118522 hasn't been fixed.
   ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri2, 0));
 
   let sslStatus = new FakeSSLStatus();
   SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                          "max-age=1000;includeSubdomains", sslStatus, 0);
+                          "max-age=1000;includeSubdomains", sslStatus, 0,
+                          Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
   ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri1, 0));
   ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri2, 0));
 
   SSService.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0);
   ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
   ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri1, 0));
   ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri2, 0));
--- a/security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
+++ b/security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
@@ -14,28 +14,31 @@ function check_ip(s, v, ip) {
   str += "/";
 
   let uri = Services.io.newURI(str);
   ok(!s.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
 
   let parsedMaxAge = {};
   let parsedIncludeSubdomains = {};
   s.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                  "max-age=1000;includeSubdomains", sslStatus, 0, {},
+                  "max-age=1000;includeSubdomains", sslStatus, 0,
+                  Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST, {},
                   parsedMaxAge, parsedIncludeSubdomains);
   ok(!s.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
      "URI should not be secure if it contains an IP address");
 
   /* Test that processHeader will ignore headers for an uri, if the uri
    * contains an IP address not a hostname.
    * If processHeader indeed ignore the header, then the output parameters will
    * remain empty, and we shouldn't see the values passed as the header.
    */
   notEqual(parsedMaxAge.value, 1000);
   notEqual(parsedIncludeSubdomains.value, true);
+  notEqual(parsedMaxAge.value, undefined);
+  notEqual(parsedIncludeSubdomains.value, undefined);
 }
 
 function run_test() {
   let SSService = Cc["@mozilla.org/ssservice;1"]
                     .getService(Ci.nsISiteSecurityService);
 
   check_ip(SSService, 4, "127.0.0.1");
   check_ip(SSService, 4, "10.0.0.1");
--- a/security/manager/ssl/tests/unit/test_sts_parser.js
+++ b/security/manager/ssl/tests/unit/test_sts_parser.js
@@ -12,31 +12,33 @@ let sss = Cc["@mozilla.org/ssservice;1"]
 let sslStatus = new FakeSSLStatus();
 
 function testSuccess(header, expectedMaxAge, expectedIncludeSubdomains) {
   let dummyUri = Services.io.newURI("https://foo.com/bar.html");
   let maxAge = {};
   let includeSubdomains = {};
 
   sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, dummyUri, header,
-                    sslStatus, 0, {}, maxAge, includeSubdomains);
+                    sslStatus, 0, sss.SOURCE_ORGANIC_REQUEST, {}, maxAge,
+                    includeSubdomains);
 
   equal(maxAge.value, expectedMaxAge, "Did not correctly parse maxAge");
   equal(includeSubdomains.value, expectedIncludeSubdomains,
         "Did not correctly parse presence/absence of includeSubdomains");
 }
 
 function testFailure(header) {
   let dummyUri = Services.io.newURI("https://foo.com/bar.html");
   let maxAge = {};
   let includeSubdomains = {};
 
   throws(() => {
     sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, dummyUri, header,
-                      sslStatus, 0, {}, maxAge, includeSubdomains);
+                      sslStatus, 0, sss.SOURCE_ORGANIC_REQUEST, {}, maxAge,
+                      includeSubdomains);
   }, "Parsed invalid header: " + header);
 }
 
 function run_test() {
     // SHOULD SUCCEED:
     testSuccess("max-age=100", 100, false);
     testSuccess("max-age  =100", 100, false);
     testSuccess(" max-age=100", 100, false);
--- a/security/manager/ssl/tests/unit/test_sts_preload_dynamic.js
+++ b/security/manager/ssl/tests/unit/test_sts_preload_dynamic.js
@@ -55,13 +55,14 @@ function run_test() {
 
   // check that it's now including subdomains
   ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, subDomainUri,
                            0));
 
   // Now let's simulate overriding the entry by setting an entry from a header
   // with max-age set to 0
   SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                          "max-age=0", sslStatus, 0);
+                          "max-age=0", sslStatus, 0,
+                          Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
 
   // this should no longer be an HSTS host
   ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
 }
--- a/security/manager/ssl/tests/unit/test_sts_preloadlist_perwindowpb.js
+++ b/security/manager/ssl/tests/unit/test_sts_preloadlist_perwindowpb.js
@@ -72,43 +72,47 @@ function test_part1() {
        Services.io.newURI("https://notsts.nonexistent.example.com."), 0));
 
   // check that processing a header with max-age: 0 will remove a preloaded
   // site from the list
   let uri = Services.io.newURI("https://includesubdomains.preloaded.test");
   let subDomainUri =
     Services.io.newURI("https://subdomain.includesubdomains.preloaded.test");
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", sslStatus, 0);
+                           "max-age=0", sslStatus, 0,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
   ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
                              subDomainUri, 0));
   // check that processing another header (with max-age non-zero) will
   // re-enable a site's sts status
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1000", sslStatus, 0);
+                           "max-age=1000", sslStatus, 0,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
   // but this time include subdomains was not set, so test for that
   ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
                              subDomainUri, 0));
   gSSService.clearAll();
 
   // check that processing a header with max-age: 0 from a subdomain of a site
   // will not remove that (ancestor) site from the list
   uri = Services.io.newURI("https://subdomain.noincludesubdomains.preloaded.test");
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", sslStatus, 0);
+                           "max-age=0", sslStatus, 0,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
                             Services.io.newURI("https://noincludesubdomains.preloaded.test"),
                             0));
   ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
 
   uri = Services.io.newURI("https://subdomain.includesubdomains.preloaded.test");
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", sslStatus, 0);
+                           "max-age=0", sslStatus, 0,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   // we received a header with "max-age=0", so we have "no information"
   // regarding the sts state of subdomain.includesubdomains.preloaded.test specifically,
   // but it is actually still an STS host, because of the preloaded
   // includesubdomains.preloaded.test including subdomains.
   // Here's a drawing:
   // |-- includesubdomains.preloaded.test (in preload list, includes subdomains) IS sts host
   //     |-- subdomain.includesubdomains.preloaded.test                          IS sts host
   //     |   `-- another.subdomain.includesubdomains.preloaded.test              IS sts host
@@ -123,17 +127,18 @@ function test_part1() {
        Ci.nsISiteSecurityService.HEADER_HSTS,
        Services.io.newURI("https://sibling.includesubdomains.preloaded.test"), 0));
   ok(gSSService.isSecureURI(
        Ci.nsISiteSecurityService.HEADER_HSTS,
        Services.io.newURI("https://another.subdomain.includesubdomains.preloaded.test"),
        0));
 
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1000", sslStatus, 0);
+                           "max-age=1000", sslStatus, 0,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   // Here's what we have now:
   // |-- includesubdomains.preloaded.test (in preload list, includes subdomains) IS sts host
   //     |-- subdomain.includesubdomains.preloaded.test (include subdomains is false) IS sts host
   //     |   `-- another.subdomain.includesubdomains.preloaded.test              IS NOT sts host
   //     `-- sibling.includesubdomains.preloaded.test                            IS sts host
   ok(gSSService.isSecureURI(
        Ci.nsISiteSecurityService.HEADER_HSTS,
        Services.io.newURI("https://subdomain.includesubdomains.preloaded.test"), 0));
@@ -149,17 +154,18 @@ function test_part1() {
   // identifying a host that is on the preload list as no longer sts.
   // (This happens when we're in regular browsing mode, we get a header from
   // a site on the preload list, and that header later expires. We need to
   // then treat that host as no longer an sts host.)
   // (sanity check first - this should be in the preload list)
   uri = Services.io.newURI("https://includesubdomains2.preloaded.test");
   ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1", sslStatus, 0);
+                           "max-age=1", sslStatus, 0,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   do_timeout(1250, function() {
     ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
     run_next_test();
   });
 }
 
 const IS_PRIVATE = Ci.nsISocketProvider.NO_PERMANENT_STORAGE;
 
@@ -170,49 +176,53 @@ function test_private_browsing1() {
     Services.io.newURI("https://a.b.c.subdomain.includesubdomains.preloaded.test");
   // sanity - includesubdomains.preloaded.test is preloaded, includeSubdomains set
   ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
                             IS_PRIVATE));
   ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, subDomainUri,
                             IS_PRIVATE));
 
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", sslStatus, IS_PRIVATE);
+                           "max-age=0", sslStatus, IS_PRIVATE,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
                              IS_PRIVATE));
   ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
                              subDomainUri, IS_PRIVATE));
 
   // check adding it back in
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1000", sslStatus, IS_PRIVATE);
+                           "max-age=1000", sslStatus, IS_PRIVATE,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, IS_PRIVATE));
   // but no includeSubdomains this time
   ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
                              subDomainUri, IS_PRIVATE));
 
   // do the hokey-pokey...
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", sslStatus, IS_PRIVATE);
+                           "max-age=0", sslStatus, IS_PRIVATE,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
                              IS_PRIVATE));
   ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
                              subDomainUri, IS_PRIVATE));
 
   // Test that an expired private browsing entry results in correctly
   // identifying a host that is on the preload list as no longer sts.
   // (This happens when we're in private browsing mode, we get a header from
   // a site on the preload list, and that header later expires. We need to
   // then treat that host as no longer an sts host.)
   // (sanity check first - this should be in the preload list)
   uri = Services.io.newURI("https://includesubdomains2.preloaded.test");
   ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
                             IS_PRIVATE));
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1", sslStatus, IS_PRIVATE);
+                           "max-age=1", sslStatus, IS_PRIVATE,
+                           Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
   do_timeout(1250, function() {
     ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
                                IS_PRIVATE));
     // Simulate leaving private browsing mode
     Services.obs.notifyObservers(null, "last-pb-context-exited");
   });
 }
 
--- a/security/manager/tools/getHSTSPreloadList.js
+++ b/security/manager/tools/getHSTSPreloadList.js
@@ -111,18 +111,19 @@ function processStsHeader(host, header, 
   var includeSubdomains = { value: false };
   var error = ERROR_NONE;
   if (header != null && securityInfo != null) {
     try {
       var uri = Services.io.newURI("https://" + host.name);
       var sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
                                   .SSLStatus;
       gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS,
-                               uri, header, sslStatus, 0, {}, maxAge,
-                               includeSubdomains);
+                               uri, header, sslStatus, 0,
+                               Ci.nsISiteSecurityService.SOURCE_PRELOAD_LIST,
+                               {}, maxAge, includeSubdomains);
     } catch (e) {
       dump("ERROR: could not process header '" + header + "' from " +
            host.name + ": " + e + "\n");
       error = e;
     }
   } else if (status == 0) {
     error = ERROR_CONNECTING_TO_HOST;
   } else {
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -10207,16 +10207,25 @@
     "expires_in_version": "62",
     "kind": "exponential",
     "low": 100,
     "high": 30000,
     "n_buckets": 100,
     "keyed": true,
     "description": "The amount of time required for HSTS priming requests (ms), keyed by success or failure of the priming request. (success, failure)"
   },
+  "HSTS_UPGRADE_SOURCE": {
+    "record_in_processes": [ "main" ],
+    "alert_emails": ["seceng-telemetry@mozilla.com"],
+    "bug_numbers": [1363546],
+    "expires_in_version": "62",
+    "kind": "enumerated",
+    "n_values": 8,
+    "description": "When we record an upgrade due to HSTS, record the source of that HSTS entry. (0: HSTS preload list, 1: HSTS Header seen naturally, 2: HSTS priming)"
+  },
   "MIXED_CONTENT_OBJECT_SUBREQUEST": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["seceng-telemetry@mozilla.com"],
     "bug_numbers": [1244116],
     "expires_in_version": "60",
     "kind": "enumerated",
     "n_values": 10,
     "description": "How often objects load insecure content on secure pages (counting pages, not objects). 0=pages with no mixed object subrequests, 1=pages with mixed object subrequests"