Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
http-observatory/httpobs/scanner/grader/grade.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
429 lines (402 sloc)
16.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
GRADE_CHART = { | |
100: 'A+', | |
95: 'A', | |
90: 'A', | |
85: 'A-', | |
80: 'B+', | |
75: 'B', | |
70: 'B', | |
65: 'B-', | |
60: 'C+', | |
55: 'C', | |
50: 'C', | |
45: 'C-', | |
40: 'D+', | |
35: 'D', | |
30: 'D', | |
25: 'D-', | |
20: 'F', | |
15: 'F', | |
10: 'F', | |
5: 'F', | |
0: 'F' | |
} | |
# See https://wiki.mozilla.org/Security/Standard_Levels for a definition of the risk levels | |
# We cannot make an accurate decision on HIGH and MAXIMUM risk likelihood indicators with the current checks, | |
# thus the likelihood indicator is currently at best (or worse) MEDIUM. Modifiers (A-A+B+B-, ... are normalized | |
# A,B, ...) in the calling function. | |
LIKELIHOOD_INDICATOR_CHART = { | |
'A': 'LOW', | |
'B': 'MEDIUM', | |
'C': 'MEDIUM', | |
'D': 'MEDIUM', | |
'F': 'MEDIUM' | |
} | |
# The minimum required score to receive extra credit | |
MINIMUM_SCORE_FOR_EXTRA_CREDIT = 90 | |
GRADES = set(GRADE_CHART.values()) | |
SCORE_TABLE = { | |
# contribute.json | |
'contribute-json-with-required-keys': { | |
'description': 'Contribute.json implemented with the required contact information', | |
'modifier': 0, | |
}, | |
'contribute-json-only-required-on-mozilla-properties': { | |
'description': 'Contribute.json isn\'t required on websites that don\'t belong to Mozilla', | |
'modifier': 0, | |
}, | |
'contribute-json-missing-required-keys': { | |
'description': 'Contribute.json exists, but is missing some of the required keys', | |
'modifier': -5, | |
}, | |
'contribute-json-not-implemented': { | |
'description': 'Contribute.json file missing from root of website', | |
'modifier': -5, | |
}, | |
'contribute-json-invalid-json': { | |
'description': 'Contribute.json file cannot be parsed', | |
'modifier': -10, | |
}, | |
# CSP | |
'csp-implemented-with-no-unsafe-default-src-none': { | |
'description': 'Content Security Policy (CSP) implemented with default-src \'none\' and no \'unsafe\'', | |
'modifier': 10, | |
}, | |
'csp-implemented-with-no-unsafe': { | |
'description': 'Content Security Policy (CSP) implemented without \'unsafe-inline\' or \'unsafe-eval\'', | |
'modifier': 5, | |
}, | |
'csp-implemented-with-unsafe-inline-in-style-src-only': { | |
'description': ('Content Security Policy (CSP) implemented with unsafe sources inside style-src. ' | |
'This includes \'unsafe-inline\', data: or overly broad sources such as https:.'), | |
'modifier': 0, | |
}, | |
'csp-implemented-with-insecure-scheme-in-passive-content-only': { | |
'description': ('Content Security Policy (CSP) implemented, ' | |
'but secure site allows images or media to be loaded over HTTP'), | |
'modifier': -10, | |
}, | |
'csp-implemented-with-unsafe-eval': { | |
'description': 'Content Security Policy (CSP) implemented, but allows \'unsafe-eval\'', | |
'modifier': -10, | |
}, | |
'csp-implemented-with-unsafe-inline': { | |
'description': ('Content Security Policy (CSP) implemented unsafely. ' | |
'This includes \'unsafe-inline\' or data: inside script-src, ' | |
'overly broad sources such as https: inside object-src or script-src, ' | |
'or not restricting the sources for object-src or script-src.'), | |
'modifier': -20, | |
}, | |
'csp-implemented-with-insecure-scheme': { | |
'description': ('Content Security Policy (CSP) implemented, ' | |
'but secure site allows resources to be loaded over HTTP'), | |
'modifier': -20, | |
}, | |
'csp-header-invalid': { | |
'description': 'Content Security Policy (CSP) header cannot be parsed successfully', | |
'modifier': -25, | |
}, | |
'csp-not-implemented': { | |
'description': 'Content Security Policy (CSP) header not implemented', | |
'modifier': -25, | |
}, | |
# Cookies | |
'cookies-secure-with-httponly-sessions-and-samesite': { | |
'description': ('All cookies use the Secure flag, session cookies use the HttpOnly flag, and cross-origin ' | |
'restrictions are in place via the SameSite flag'), | |
'modifier': 5, | |
}, | |
'cookies-secure-with-httponly-sessions': { | |
'description': 'All cookies use the Secure flag and all session cookies use the HttpOnly flag', | |
'modifier': 0, | |
}, | |
'cookies-not-found': { | |
'description': 'No cookies detected', | |
'modifier': 0, | |
}, | |
'cookies-without-secure-flag-but-protected-by-hsts': { | |
'description': 'Cookies set without using the Secure flag, but transmission over HTTP prevented by HSTS', | |
'modifier': -5, | |
}, | |
'cookies-session-without-secure-flag-but-protected-by-hsts': { | |
'description': 'Session cookie set without the Secure flag, but transmission over HTTP prevented by HSTS', | |
'modifier': -10, | |
}, | |
'cookies-without-secure-flag': { | |
'description': 'Cookies set without using the Secure flag or set over HTTP', | |
'modifier': -20, | |
}, | |
'cookies-samesite-flag-invalid': { | |
'description': 'Cookies use SameSite flag, but set to something other than Strict or Lax', | |
'modifier': -20, | |
}, | |
'cookies-anticsrf-without-samesite-flag': { | |
'description': 'Anti-CSRF tokens set without using the SameSite flag', | |
'modifier': -20, | |
}, | |
'cookies-session-without-httponly-flag': { | |
'description': 'Session cookie set without using the HttpOnly flag', | |
'modifier': -30, | |
}, | |
'cookies-session-without-secure-flag': { | |
'description': 'Session cookie set without using the Secure flag or set over HTTP', | |
'modifier': -40, | |
}, | |
# Cross-origin resource sharing | |
'cross-origin-resource-sharing-not-implemented': { | |
'description': 'Content is not visible via cross-origin resource sharing (CORS) files or headers', | |
'modifier': 0, | |
}, | |
'cross-origin-resource-sharing-implemented-with-public-access': { | |
'description': ('Public content is visible via cross-origin resource sharing (CORS) ' | |
'Access-Control-Allow-Origin header'), | |
'modifier': 0, | |
}, | |
'cross-origin-resource-sharing-implemented-with-restricted-access': { | |
'description': ('Content is visible via cross-origin resource sharing (CORS) files or headers, ' | |
'but is restricted to specific domains'), | |
'modifier': 0, | |
}, | |
'cross-origin-resource-sharing-implemented-with-universal-access': { | |
'description': 'Content is visible via cross-origin resource sharing (CORS) file or headers', | |
'modifier': -50, | |
}, | |
# Public Key Pinning | |
'hpkp-preloaded': { | |
'description': 'Preloaded via the HTTP Public Key Pinning (HPKP) preloading process', | |
'modifier': 0, | |
}, | |
'hpkp-implemented-max-age-at-least-fifteen-days': { | |
'description': 'HTTP Public Key Pinning (HPKP) header set to a minimum of 15 days (1296000)', | |
'modifier': 0, | |
}, | |
'hpkp-implemented-max-age-less-than-fifteen-days': { | |
'description': 'HTTP Public Key Pinning (HPKP) header set to less than 15 days (1296000)', | |
'modifier': 0, | |
}, | |
'hpkp-not-implemented': { | |
'description': 'HTTP Public Key Pinning (HPKP) header not implemented', | |
'modifier': 0, | |
}, | |
'hpkp-not-implemented-no-https': { | |
'description': 'HTTP Public Key Pinning (HPKP) header can\'t be implemented without HTTPS', | |
'modifier': 0, | |
}, | |
'hpkp-invalid-cert': { | |
'description': ('HTTP Public Key Pinning (HPKP) header cannot be set, ' | |
'as site contains an invalid certificate chain'), | |
'modifier': 0, | |
}, | |
'hpkp-header-invalid': { | |
'description': 'HTTP Public Key Pinning (HPKP) header cannot be recognized', | |
'modifier': -5, | |
}, | |
# Redirection | |
'redirection-all-redirects-preloaded': { | |
'description': 'All hosts redirected to are in the HTTP Strict Transport Security (HSTS) preload list', | |
'modifier': 0, | |
}, | |
'redirection-to-https': { | |
'description': 'Initial redirection is to HTTPS on same host, final destination is HTTPS', | |
'modifier': 0, | |
}, | |
'redirection-not-needed-no-http': { | |
'description': 'Not able to connect via HTTP, so no redirection necessary', | |
'modifier': 0, | |
}, | |
'redirection-off-host-from-http': { | |
'description': 'Initial redirection from HTTP to HTTPS is to a different host, preventing HSTS', | |
'modifier': -5, | |
}, | |
'redirection-not-to-https-on-initial-redirection': { | |
'description': 'Redirects to HTTPS eventually, but initial redirection is to another HTTP URL', | |
'modifier': -10, | |
}, | |
'redirection-not-to-https': { | |
'description': 'Redirects, but final destination is not an HTTPS URL', | |
'modifier': -20, | |
}, | |
'redirection-missing': { | |
'description': 'Does not redirect to an HTTPS site', | |
'modifier': -20, | |
}, | |
'redirection-invalid-cert': { | |
'description': 'Invalid certificate chain encountered during redirection', | |
'modifier': -20, | |
}, | |
# Referrer Policy | |
'referrer-policy-private': { | |
'description': ('Referrer-Policy header set to "no-referrer", "same-origin", "strict-origin" or ' | |
'"strict-origin-when-cross-origin"'), | |
'modifier': 5, | |
}, | |
'referrer-policy-no-referrer-when-downgrade': { | |
'description': 'Referrer-Policy header set to "no-referrer-when-downgrade"', | |
'modifier': 0, | |
}, | |
'referrer-policy-not-implemented': { | |
'description': 'Referrer-Policy header not implemented', | |
'modifier': 0, | |
}, | |
'referrer-policy-unsafe': { | |
'description': 'Referrer-Policy header set unsafely to "origin", "origin-when-cross-origin", or "unsafe-url"', | |
'modifier': -5, | |
}, | |
'referrer-policy-header-invalid': { | |
'description': 'Referrer-Policy header cannot be recognized', | |
'modifier': -5, | |
}, | |
# Strict Transport Security (HSTS) | |
'hsts-preloaded': { | |
'description': 'Preloaded via the HTTP Strict Transport Security (HSTS) preloading process', | |
'modifier': 5, | |
}, | |
'hsts-implemented-max-age-at-least-six-months': { | |
'description': 'HTTP Strict Transport Security (HSTS) header set to a minimum of six months (15768000)', | |
'modifier': 0, | |
}, | |
'hsts-implemented-max-age-less-than-six-months': { | |
'description': 'HTTP Strict Transport Security (HSTS) header set to less than six months (15768000)', | |
'modifier': -10, | |
}, | |
'hsts-not-implemented': { | |
'description': 'HTTP Strict Transport Security (HSTS) header not implemented', | |
'modifier': -20, | |
}, | |
'hsts-header-invalid': { | |
'description': 'HTTP Strict Transport Security (HSTS) header cannot be recognized', | |
'modifier': -20, | |
}, | |
'hsts-not-implemented-no-https': { | |
'description': 'HTTP Strict Transport Security (HSTS) header cannot be set for sites not available over HTTPS', | |
'modifier': -20, | |
}, | |
'hsts-invalid-cert': { | |
'description': ('HTTP Strict Transport Security (HSTS) header cannot be set, ' | |
'as site contains an invalid certificate chain'), | |
'modifier': -20, | |
}, | |
# Subresource Integrity (SRI) | |
'sri-implemented-and-all-scripts-loaded-securely': { | |
'description': 'Subresource Integrity (SRI) is implemented and all scripts are loaded from a similar origin', | |
'modifier': 5, | |
}, | |
'sri-implemented-and-external-scripts-loaded-securely': { | |
'description': 'Subresource Integrity (SRI) is implemented and all scripts are loaded securely', | |
'modifier': 5, | |
}, | |
'sri-not-implemented-response-not-html': { | |
'description': 'Subresource Integrity (SRI) is only needed for html resources', | |
'modifier': 0, | |
}, | |
'sri-not-implemented-but-no-scripts-loaded': { | |
'description': 'Subresource Integrity (SRI) is not needed since site contains no script tags', | |
'modifier': 0, | |
}, | |
'sri-not-implemented-but-all-scripts-loaded-from-secure-origin': { | |
'description': 'Subresource Integrity (SRI) not implemented, but all scripts are loaded from a similar origin', | |
'modifier': 0, | |
}, | |
'sri-not-implemented-but-external-scripts-loaded-securely': { | |
'description': 'Subresource Integrity (SRI) not implemented, but all external scripts are loaded over HTTPS', | |
'modifier': -5, | |
}, | |
'sri-implemented-but-external-scripts-not-loaded-securely': { | |
'description': ('Subresource Integrity (SRI) implemented, but external scripts are loaded over HTTP or use ' | |
'protocol-relative URLs via src="//..."'), | |
'modifier': -20, | |
}, | |
'sri-not-implemented-and-external-scripts-not-loaded-securely': { | |
'description': ('Subresource Integrity (SRI) not implemented, and external scripts are loaded over HTTP or ' | |
'use protocol-relative URLs via src="//..."'), | |
'modifier': -50, | |
}, | |
# X-Content-Type-Options | |
'x-content-type-options-nosniff': { | |
'description': 'X-Content-Type-Options header set to "nosniff"', | |
'modifier': 0, | |
}, | |
'x-content-type-options-not-implemented': { | |
'description': 'X-Content-Type-Options header not implemented', | |
'modifier': -5, | |
}, | |
'x-content-type-options-header-invalid': { | |
'description': 'X-Content-Type-Options header cannot be recognized', | |
'modifier': -5, | |
}, | |
# X-Frame-Options | |
'x-frame-options-implemented-via-csp': { | |
'description': 'X-Frame-Options (XFO) implemented via the CSP frame-ancestors directive', | |
'modifier': 5, | |
}, | |
'x-frame-options-sameorigin-or-deny': { | |
'description': 'X-Frame-Options (XFO) header set to SAMEORIGIN or DENY', | |
'modifier': 0, | |
}, | |
'x-frame-options-allow-from-origin': { | |
'description': 'X-Frame-Options (XFO) header uses ALLOW-FROM uri directive', | |
'modifier': 0, | |
}, | |
'x-frame-options-not-implemented': { | |
'description': 'X-Frame-Options (XFO) header not implemented', | |
'modifier': -20, | |
}, | |
'x-frame-options-header-invalid': { | |
'description': 'X-Frame-Options (XFO) header cannot be recognized', | |
'modifier': -20, | |
}, | |
# X-XSS-Protection | |
'x-xss-protection-enabled-mode-block': { | |
'description': 'X-XSS-Protection header set to "1; mode=block"', | |
'modifier': 0, | |
}, | |
'x-xss-protection-enabled': { | |
'description': 'X-XSS-Protection header set to "1"', | |
'modifier': 0, | |
}, | |
'x-xss-protection-not-needed-due-to-csp': { | |
'description': 'X-XSS-Protection header not needed due to strong Content Security Policy (CSP) header', | |
'modifier': 0, | |
}, | |
'x-xss-protection-disabled': { | |
'description': 'X-XSS-Protection header set to "0" (disabled)', | |
'modifier': -10, | |
}, | |
'x-xss-protection-not-implemented': { | |
'description': 'X-XSS-Protection header not implemented', | |
'modifier': -10, | |
}, | |
'x-xss-protection-header-invalid': { | |
'description': 'X-XSS-Protection header cannot be recognized', | |
'modifier': -10, | |
}, | |
# Generic results | |
'html-not-parsable': { | |
'description': 'Claims to be html, but cannot be parsed', | |
'modifier': -20, # can't run an SRI check if the HTML isn't parsable | |
}, | |
'request-did-not-return-status-code-200': { | |
'description': 'Site did not return a status code of 200', | |
'modifier': -5, # can't run an SRI check on pages that don't return a 200 (deprecated) | |
}, | |
'xml-not-parsable': { | |
'description': 'Claims to be xml, but cannot be parsed', | |
'modifier': -20, # can't run an ACAO check if the xml files can't be parsed | |
} | |
} | |
def get_grade_and_likelihood_for_score(score: int) -> tuple: | |
""" | |
:param score: raw score based on all of the tests | |
:return: the overall test score, grade and likelihood_indicator | |
""" | |
score = max(score, 0) # can't have scores below 0 | |
# If it's >100, just use the grade for 100, otherwise round down to the nearest multiple of 5 | |
grade = GRADE_CHART[min(score - score % 5, 100)] | |
# If GRADE_CHART and LIKELIHOOD_INDICATOR_CHART are not synchronized during | |
# manual code updates, then default to UNKNOWN | |
likelihood_indicator = LIKELIHOOD_INDICATOR_CHART.get(grade[0], 'UNKNOWN') | |
return score, grade, likelihood_indicator | |
def get_score_description(result) -> str: | |
return SCORE_TABLE[result]['description'] | |
def get_score_modifier(result) -> int: | |
return SCORE_TABLE[result]['modifier'] |