/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* The Security devtool supports the following arguments:
* * Security CSP
* Provides feedback about the current CSP
*
* * Security referrer
* Provides information about the current referrer policy
*/
"use strict";
const { Cc, Ci, Cu, CC } = require("chrome");
const l10n = require("gcli/l10n");
const CSP = Cc["@mozilla.org/cspcontext;1"].getService(Ci.nsIContentSecurityPolicy);
const GOOD_IMG_SRC = "chrome://browser/content/gcli_sec_good.svg";
const MOD_IMG_SRC = "chrome://browser/content/gcli_sec_moderate.svg";
const BAD_IMG_SRC = "chrome://browser/content/gcli_sec_bad.svg";
// special handling within policy
const POLICY_REPORT_ONLY = "report-only"
// special handling of directives
const DIR_UPGRADE_INSECURE = "upgrade-insecure-requests";
const DIR_BLOCK_ALL_MIXED_CONTENT = "block-all-mixed-content";
// special handling of sources
const SRC_UNSAFE_INLINE = "'unsafe-inline'";
const SRC_UNSAFE_EVAL = "'unsafe-eval'";
const WILDCARD_MSG = l10n.lookup("securityCSPRemWildCard");
const XSS_WARNING_MSG = l10n.lookup("securityCSPPotentialXSS");
const NO_CSP_ON_PAGE_MSG = l10n.lookup("securityCSPNoCSPOnPage");
const CONTENT_SECURITY_POLICY_MSG = l10n.lookup("securityCSPHeaderOnPage");
const CONTENT_SECURITY_POLICY_REPORT_ONLY_MSG = l10n.lookup("securityCSPROHeaderOnPage");
const NEXT_URI_HEADER = l10n.lookup("securityReferrerNextURI");
const CALCULATED_REFERRER_HEADER = l10n.lookup("securityReferrerCalculatedReferrer");
/* The official names from the W3C Referrer Policy Draft http://www.w3.org/TR/referrer-policy/ */
const REFERRER_POLICY_NAMES = [ "None When Downgrade (default)", "None", "Origin Only", "Origin When Cross-Origin", "Unsafe URL" ];
exports.items = [
{
// --- General Security information
name: "security",
description: l10n.lookup("securityPrivacyDesc"),
manual: l10n.lookup("securityManual")
},
{
// --- CSP specific Security information
item: "command",
runAt: "server",
name: "security csp",
description: l10n.lookup("securityCSPDesc"),
manual: l10n.lookup("securityCSPManual"),
returnType: "securityCSPInfo",
exec: function(args, context) {
var cspJSON = context.environment.document.nodePrincipal.cspJSON;
var cspOBJ = JSON.parse(cspJSON);
var outPolicies = [];
var policies = cspOBJ["csp-policies"];
// loop over all the different policies
for (var csp in policies) {
var curPolicy = policies[csp];
// loop over all the directive-values within that policy
var outDirectives = [];
var outHeader = CONTENT_SECURITY_POLICY_MSG;
for (var dir in curPolicy) {
var curDir = curPolicy[dir];
// when iterating properties within the obj we might also
// encounter the 'report-only' flag, which is not a csp directive.
if (dir === POLICY_REPORT_ONLY) {
outHeader = curPolicy[POLICY_REPORT_ONLY] === true ?
CONTENT_SECURITY_POLICY_REPORT_ONLY_MSG :
CONTENT_SECURITY_POLICY_MSG;
continue;
}
// loop over all the directive-sources within that directive
var outSrcs = [];
// special case handling for the directives
// upgrade-insecure-requests and block-all-mixed-content
// which do not include any srcs
if (dir === DIR_UPGRADE_INSECURE ||
dir === DIR_BLOCK_ALL_MIXED_CONTENT) {
outSrcs.push({
icon: GOOD_IMG_SRC,
src: "", // no src
desc: "" // no description
});
}
for (var src in curDir) {
var curSrc = curDir[src];
// the default icon and descritpion of the directive-src
var outIcon = GOOD_IMG_SRC;
var outDesc = "";
if (curSrc.indexOf("*") > -1) {
outIcon = MOD_IMG_SRC;
outDesc = WILDCARD_MSG;
}
if (curSrc == SRC_UNSAFE_INLINE || curSrc == SRC_UNSAFE_EVAL) {
outIcon = BAD_IMG_SRC;
outDesc = XSS_WARNING_MSG;
}
outSrcs.push({
icon: outIcon,
src: curSrc,
desc: outDesc
});
}
// append info about that directive to the directives array
outDirectives.push({
dirValue: dir,
dirSrc: outSrcs
});
}
// append info about the policy to the policies array
outPolicies.push({
header: outHeader,
directives: outDirectives
});
}
return outPolicies;
}
},
{
item: "converter",
from: "securityCSPInfo",
to: "view",
exec: function(cspInfo, context) {
var url = context.environment.target.url;
if (cspInfo.length == 0) {
return context.createView({
html:
"
" +
" " +
" | " +
" " + NO_CSP_ON_PAGE_MSG + " " + url + " | " +
"
" +
"
"});
}
return context.createView({
html:
"" +
// iterate all policies
" " +
" ${csp.header} " + url + "
" +
" " +
// >> iterate all directives
" " +
" ${dir.dirValue} | " +
" " +
" " +
// >> >> iterate all srs
" " +
" | " +
" ${src.src} | " +
" ${src.desc} | " +
" " +
" " +
" | " +
" " +
" " +
" | " +
"
" +
"
",
data: {
cspinfo: cspInfo,
}
});
}
},
{
// --- Referrer Policy specific Security information
item: "command",
runAt: "server",
name: "security referrer",
description: l10n.lookup("securityReferrerPolicyDesc"),
manual: l10n.lookup("securityReferrerPolicyManual"),
returnType: "securityReferrerPolicyInfo",
exec: function(args, context) {
var doc = context.environment.document;
var referrerPolicy = doc.referrerPolicy;
var pageURI = doc.documentURIObject;
var sameDomainReferrer = "";
var otherDomainReferrer = "";
var downgradeReferrer = "";
var otherDowngradeReferrer = "";
var origin = pageURI.prePath;
switch (referrerPolicy) {
case Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER:
// sends no referrer
sameDomainReferrer
= otherDomainReferrer
= downgradeReferrer
= otherDowngradeReferrer
= "(no referrer)";
break;
case Ci.nsIHttpChannel.REFERRER_POLICY_ORIGIN:
// only sends the origin of the referring URL
sameDomainReferrer
= otherDomainReferrer
= downgradeReferrer
= otherDowngradeReferrer
= origin;
break;
case Ci.nsIHttpChannel.REFERRER_POLICY_ORIGIN_WHEN_XORIGIN:
// same as default, but reduced to ORIGIN when cross-origin.
sameDomainReferrer = pageURI.spec;
otherDomainReferrer
= downgradeReferrer
= otherDowngradeReferrer
= origin;
break;
case Ci.nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL:
// always sends the referrer, even on downgrade.
sameDomainReferrer
= otherDomainReferrer
= downgradeReferrer
= otherDowngradeReferrer
= pageURI.spec;
break;
case Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE:
// default state, doesn't send referrer from https->http
sameDomainReferrer = otherDomainReferrer = pageURI.spec;
downgradeReferrer = otherDowngradeReferrer = "(no referrer)";
break;
default:
// this is a new referrer policy which we do not know about
sameDomainReferrer
= otherDomainReferrer
= downgradeReferrer
= otherDowngradeReferrer
= "(unknown Referrer Policy)";
break;
}
var sameDomainUri = origin + "/*";
var referrerUrls = [
// add the referrer uri 'referrer' we would send when visiting 'uri'
{
uri: pageURI.scheme+'://example.com/',
referrer: otherDomainReferrer,
description: l10n.lookup('securityReferrerPolicyOtherDomain')},
{
uri: sameDomainUri,
referrer: sameDomainReferrer,
description: l10n.lookup('securityReferrerPolicySameDomain')}
];
if (pageURI.schemeIs('https')) {
// add the referrer we would send on downgrading http->https
if (sameDomainReferrer != downgradeReferrer) {
referrerUrls.push({
uri: "http://"+pageURI.hostPort+"/*",
referrer: downgradeReferrer,
description:
l10n.lookup('securityReferrerPolicySameDomainDowngrade')
});
}
if (otherDomainReferrer != otherDowngradeReferrer) {
referrerUrls.push({
uri: "http://example.com/",
referrer: otherDowngradeReferrer,
description:
l10n.lookup('securityReferrerPolicyOtherDomainDowngrade')
});
}
}
return {
header: l10n.lookupFormat("securityReferrerPolicyReportHeader",
[pageURI.spec]),
policyName: REFERRER_POLICY_NAMES[referrerPolicy],
urls: referrerUrls
}
}
},
{
item: "converter",
from: "securityReferrerPolicyInfo",
to: "view",
exec: function(referrerPolicyInfo, context) {
return context.createView({
html:
"" +
"
${rpi.header} " +
" ${rpi.policyName}
" +
"
" +
" " +
" " + NEXT_URI_HEADER + " | " +
" " + CALCULATED_REFERRER_HEADER + " | " +
"
" +
// iterate all policies
" " +
" ${nextURI.description} (e.g., ${nextURI.uri}) | " +
" ${nextURI.referrer} | " +
"
" +
"
" +
"
",
data: {
rpi: referrerPolicyInfo,
}
});
}
}
];