diff --git a/package-lock.json b/package-lock.json index 2932c6fe7cf78837fd1e29f396bdaf5c690c5651..5be09fc605277d41dcdbef7913a7c7f48efdfffb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "website-evidence-collector": "bin/website-evidence-collector.js" }, "devDependencies": { + "@prettier/plugin-pug": "^3.2.1", "@types/body-parser": "^1.19.5", "@types/express": "^5.0.0", "@types/fs-extra": "^11.0.4", @@ -1526,6 +1527,33 @@ "node": ">=8.0" } }, + "node_modules/@prettier/plugin-pug": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@prettier/plugin-pug/-/plugin-pug-3.2.1.tgz", + "integrity": "sha512-JvcW+44DtpnVsQFu++vBHl2fGVsuZC8iyxwutEUOt94thksdmXYQp1jFZWzaXckkwqLSgJHSyu19i1qvag5GJQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/Shinigami92" + }, + { + "type": "paypal", + "url": "https://www.paypal.com/donate/?hosted_button_id=L7GY729FBKTZY" + } + ], + "license": "MIT", + "dependencies": { + "pug-lexer": "^5.0.1" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, "node_modules/@puppeteer/browsers": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.7.1.tgz", diff --git a/package.json b/package.json index 2c62c30db1519e62bc383d0842720d58174fd9a0..98a94cc5a24c26b393bb6683db5c3aba864b2e27 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "yargs": "^17.7.2" }, "devDependencies": { + "@prettier/plugin-pug": "^3.2.1", "@types/body-parser": "^1.19.5", "@types/express": "^5.0.0", "@types/fs-extra": "^11.0.4", diff --git a/src/assets/template-office.pug b/src/assets/template-office.pug index 80d477de8b450398dade5df40b6f7c17ea5b8c9f..00e6534709a48acac4967ad7e6af5513eb8fe9d9 100644 --- a/src/assets/template-office.pug +++ b/src/assets/template-office.pug @@ -1,80 +1,90 @@ doctype html -html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') +html(xmlns="http://www.w3.org/1999/xhtml", xml:lang="en", lang="en") head - meta(charset='utf-8') - meta(name='generator', content='website-evidence-collector') - title #{title} (#{uri_ins}) - style(type='text/css'). - dd, p { + meta(charset="utf-8") + meta(name="generator", content="website-evidence-collector") + title #{ title } (#{ uri_ins }) + style(type="text/css"). + dd, + p { text-align: justify; } body: article header#title-block-header - div#logo + #logo img(src=`${basedir}/wec_logo.png`) h1.title= title h2.subtitle: a(href=uri_ins)= uri_ins - h1(id="sec:evidence-collection-organisation") Evidence Collection Organisation - + h1(id="sec:evidence-collection-organisation") Evidence collection organisation + table colgroup - col(style='width: 50%') - col(style='width: 50%') + col(style="width: 50%") + col(style="width: 50%") tbody tr - td Target Web Service + td Target web service td: code= uri_ins tr - td Automated Evidence Collection Start Time - td= new Date(start_time).toLocaleString() + td Automated evidence collection start time + td= new Date(start_time).toLocaleString("en-GB") tr - td Automated Evidence Collection End Time - td= new Date(end_time).toLocaleString() + td Automated evidence collection end time + td= new Date(end_time).toLocaleString("en-GB") tr - td Software Version + td Software version td= script.version.commit || script.version.npm - tr - td Software Host - td= script['host'] - h1(id="sec:automated-evidence-collection") Automated Evidence Collection - - p The automated evidence collection is carried out using the tool #[a(href="https://edps.europa.eu/press-publications/edps-inspection-software_en") website evidence collector] (also #[a(href="https://github.com/EU-EDPS/website-evidence-collector") on Github]) in version #{script.version.commit || script.version.npm} on the platform #{browser.platform.name} in version #{browser.platform.version}. The tool employs the browser #{browser.name} in version #{browser.version} for browsing the website. - - p During the browsing, the tool gathers evidence and runs a number of checks. It takes screenshots from the browser to identify potential cookie banners. It tests the use of HTTPS/SSL to check whether the website enforces a HTTPS connection. Then, the evidence collection tool scans the first web page for links to common social media and collaboration platforms for statistics on the overall use of potentially privacy-intrusive third-party web services. + h1(id="sec:automated-evidence-collection") Automated evidence collection + + p The automated evidence collection is carried out using the tool #[a(href="https://edps.europa.eu/press-publications/edps-inspection-software_en") website evidence collector], WEC (also #[a(href="https://code.europa.eu/EDPS/website-evidence-collector") on Code Europa EU]) in version #{ script.version.commit || script.version.npm } on the platform #{ browser.platform.name } in version #{ browser.platform.version }. The tool employs the browser #{ browser.name } in version #{ browser.version } for browsing the website. + + p The evidence collection tool simulates a browsing session of the web service, capturing traffic between the browser and the Internet, along with any persistent data stored in the browser. While browsing, the tool gathers evidence and performs a number of checks. + + p It captures screenshots from the browser to identify potential cookie banners. It also tests HTTPS/SSL usage to determine whether the website enforces a secure connection. Then, the evidence collection tool scans the first web page for links to common social media and collaboration platforms, gathering data on the overall use of potentially privacy-intrusive third-party web services. - p The analysis of the recorded traffic between the browser and both the target web service as well as involved third-party web services, and the browser’s persistent storage follows in a #[span.citation(data-cites="sec:traffic-and-persistent-data-analysis"): a(href="#traffic-and-persistent-data-analysis") subsequent section]. - - h2(id="sec:webpage-visit") Webpage Visit + p The recorded traffic between the browser, the target web service, and involved third-party web services, as well as the browser’s persistent storage, will be analysed in a subsequent section. + + p Generally, the tool browses a random subset of the target web service pages starting from the initial web page. However, the browsing can also include a set of predefined web pages. The exhaustive list of browsed web pages for this specific evidence collection is given in the Annex: Browsing history. + + h2(id="sec:webpage-visit") Web page visit p - | On #{new Date(start_time).toLocaleString()}, the evidence collection tool navigated the browser to #[a(href=uri_ins)= uri_ins]. The final location after potential redirects was #[a(href=uri_dest)= uri_dest]. + | On #{ new Date(start_time).toLocaleString("en-GB") }, the evidence collection tool navigated the browser to #[a(href=uri_ins)= uri_ins]. The final location after potential redirects was #[a(href=uri_dest)= uri_dest]. if script.config.screenshots | - | The evidence collection tool took two screenshots #[span.citation(data-cites="fig:screenshot-top") to cover the top of the webpage] and #[span.citation(data-cites="fig:screenshot-bottom") the bottom]. + | The evidence collection tool took two screenshots #[span.citation(data-cites="fig:screenshot-top") to cover the top of the web page] and #[span.citation(data-cites="fig:screenshot-bottom") the bottom]. if script.config.screenshots figure - img(src=`${jsondir}/screenshot-top.png` alt="Webpage Top Screenshot" id="fig:screenshot-top" style="width:100.0%") - figcaption Webpage Top Screenshot - + img(id="fig:screenshot-top", + src=`data:image/png;base64,${screenshots.screenshot_top}`, + alt="Web page top screenshot", + style="width: 100%" + ) + p Web page top screenshot figure - img(src=`${jsondir}/screenshot-bottom.png` alt="Webpage Bottom Screenshot" id="fig:screenshot-bottom" style="width:100.0%") - figcaption Webpage Bottom Screenshot - + img(id="fig:screenshot-bottom", + src=`data:image/png;base64,${screenshots.screenshot_bottom}`, + alt="Web page bottom screenshot", + style="width: 100%" + ) + p Web page bottom screenshot h2(id="sec:use-of-httpsssl") Use of HTTPS/SSL - p The evidence collection tool assessed the redirecting behaviour of #{host} with respect to the use of HTTPS. - + p HTTP (Hypertext Transfer Protocol) is a communication standard that transmits data between a website and a user’s browser in an unencrypted format, making it vulnerable to interception and eavesdropping. In contrast, HTTPS (Hypertext Transfer Protocol Secure) extends HTTP by adding an extra layer of security through encryption, which protects the confidentiality and integrity of the data exchanged between a website and a user’s browser. + + p The evidence collection tool assessed the behaviour of #{ host } with respect to the use of HTTPS. + table.use-of-httpsssl colgroup - col(style='width: 50%') - col(style='width: 50%') + col(style="width: 50%") + col(style="width: 50%") tbody tr - td allows connection with HTTPS + td Allows connection with HTTPS td= secure_connection.https_support tr td HTTP redirect to HTTPS @@ -94,9 +104,9 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') tr td Error when connecting with HTTPS td= secure_connection.https_error - + if testSSL && testSSL.scanResult[0] - - var results = testSSL.scanResult[0] + - var results = testSSL.scanResult[0]; - sortSeverity = function(a,b) { @@ -108,43 +118,79 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') OK: 4, INFO: 5, }; - + return severityToNumber[a.severity]-severityToNumber[b.severity]; } - - p The software TestSSL from #[a(href="https://testssl.sh") https://testssl.sh] inspected the HTTPS configuration of the web service host #{results.targetHost}. It classifies detected vulnerabilities by their level of severity #[em low], #[em medium], #[em high], or #[em critical]. The severity ratings are automatically computed by the TestSSL software without consideration of the specifics of the individual website. They do not reflect the opinions or views of the website evidence collector authors. Details on the findings are listed in #[span.citation(data-cites="app:testssl"): a(href="#app:testssl") the Annex]. - + + p The software TestSSL from #[a(href="https://testssl.sh") https://testssl.sh] inspected the HTTPS configuration of the web service host #{ results.targetHost }. It classifies detected vulnerabilities by their level of severity #[em low], #[em medium], #[em high], or #[em critical]. The severity ratings are automatically computed by the TestSSL software without considering the security requirements of the individual website. They do not reflect the opinions or views of the website evidence collector's authors. Details of the findings are listed in the Annex: TestSSL scan. + table.testssl-summary thead tr - th HTTPS/SSL Vulnerabilities per Severity + th HTTPS/SSL vulnerabilities per severity th.notrunc Freq. tbody - - var vulnerabilitiesBySeverity = groupBy(results.vulnerabilities,'severity'); + - var vulnerabilitiesBySeverity = groupBy(results.vulnerabilities, "severity"); tr td Critical - td.notrunc= vulnerabilitiesBySeverity['CRITCAL'] ? vulnerabilitiesBySeverity['CRITCAL'].length : 0 + td.notrunc= vulnerabilitiesBySeverity["CRITCAL"] ? vulnerabilitiesBySeverity["CRITCAL"].length : 0 tr td High - td.notrunc= vulnerabilitiesBySeverity['HIGH'] ? vulnerabilitiesBySeverity['HIGH'].length : 0 + td.notrunc= vulnerabilitiesBySeverity["HIGH"] ? vulnerabilitiesBySeverity["HIGH"].length : 0 tr td Medium - td.notrunc= vulnerabilitiesBySeverity['MEDIUM'] ? vulnerabilitiesBySeverity['MEDIUM'].length : 0 + td.notrunc= vulnerabilitiesBySeverity["MEDIUM"] ? vulnerabilitiesBySeverity["MEDIUM"].length : 0 tr td Low - td.notrunc= vulnerabilitiesBySeverity['LOW'] ? vulnerabilitiesBySeverity['LOW'].length : 0 + td.notrunc= vulnerabilitiesBySeverity["LOW"] ? vulnerabilitiesBySeverity["LOW"].length : 0 + + h2(id="sec:use-of-csp") Use of content security policies (CSPs) + + p Upon a browser's request for a web page, websites can specify a whitelist of mechanisms, domains, and subdomains in the #[a(href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP") Content Security Policy] (CSP) metadata sent along with the requested page. Browsers must respect this whitelist when embedding components such as styles, fonts, beacons, videos, and maps. - h2(id="sec:use-of-social-media") Use of Social Media and Collaboration Platforms - - if links.social.length > 0 - table.use-of-social-media-and-collaboration-platforms(style="width: 100%") + if hosts.contentSecurityPolicy.firstParty.length === 0 && hosts.contentSecurityPolicy.thirdParty.length === 0 + p No CSP metadata was found. Consequently, no restrictions apply. + + else + if hosts.contentSecurityPolicy.firstParty.length > 0 + ol + // check if host looks like a host (instead of e.g blob: data: etc.) + each host in hosts.contentSecurityPolicy.firstParty + if host.match(/[^\.]+\.[^\.]+/) + li: a(href=`http://${host}`)= host + else + li= host + + p The website has whitelisted #{ hosts.contentSecurityPolicy.firstParty.length } first-party domains and mechanisms. + else + p No CSP metadata related to first-party URLs was found. + + if hosts.contentSecurityPolicy.thirdParty.length > 0 + h4 Third-party content security policy hosts + + ol + each host in hosts.contentSecurityPolicy.thirdParty + li: a(href=`http://${host}`)= host + + p The website has whitelisted #{ hosts.contentSecurityPolicy.thirdParty.length } distinct third-party host(s). + else + p No third-party content security policy hosts were whitelisted. + + h2(id="sec:use-of-social-media") Use of social media and collaboration platforms + + p The website evidence collection tool found links from #[a(href=uri_dest)= uri_dest] to the following common social media and collaboration platforms. + + if links.social.length > 0 + table.use-of-social-media-and-collaboration-platforms( + style="width: 100%" + ) colgroup - col(style='width: 100%') - col(style='width: 100%') + col(style="width: 100%") + col(style="width: 100%") thead tr th Link URL - th Link Caption + th Link caption tbody each social in links.social tr @@ -152,75 +198,75 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td.notrunc= social.inner_text else p No corresponding links were found. - - p Common social media and collaboration platforms linked from #[a(href=uri_dest)= uri_dest] have been considered. - - h2(id="traffic-and-persistent-data-analysis") Traffic and Persistent Data Analysis - - p The evidence collection tool simulates a browsing session of the web service to analyse hereafter the recorded traffic between the browser and the Internet as well as the persistent data stored in the browser. First, the browser visited #[a(href=uri_dest)= uri_dest]. The evidence collection took #{browsing_history.length > 1 ? browsing_history.length - 1 : "no"} other web page(s) into account. Generally, predefined pages and a random subset of all first-party link targets (URLs) from the initial web page #[a(href=uri_dest)= uri_dest] are considered. The exhaustive list of browsed web pages is given in #[span.citation(data-cites="app:history"): a(href="#app:history") the Annex]. - - p The web page(s) were browsed consecutively between #{new Date(start_time).toLocaleString()} and #{new Date(end_time).toLocaleString()}. - - p During the browsing, the HTTP Header #[a(href="https://en.wikipedia.org/wiki/Do_Not_Track") Do Not Track] was #{browser.extra_headers.dnt ? 'set' : 'not set'}. - - p For the subsequent analysis, the following hosts (with their path) were defined as first-party: - + + h2#traffic-and-persistent-data-analysis Traffic and persistent data analysis + + p First, the browser visited #[a(href=uri_dest)= uri_dest]. The evidence collection navigated and collected evidence from #{ browsing_history.length > 1 ? browsing_history.length - 1 : "no" } additional web service page(s). + + p The web page(s) were browsed consecutively between #{ new Date(start_time).toLocaleString("en-GB") } and #{ new Date(end_time).toLocaleString("en-GB") }. + + p During the browsing, the HTTP Header #[a(href="https://en.wikipedia.org/wiki/Do_Not_Track") Do Not Track] was #{ browser.extra_headers.dnt ? 'set' : 'not set' }. + + p For the subsequent analysis, the following URLs (hosts with their paths) were defined as first-party: + ol each uri in uri_refs - li: a(href=uri)= uri.replace(/(^\w+:|^)\/\//, '') + li: a(href=uri)= uri.replace(/(^\w+:|^)\/\//, "") - h3(id="sec:traffic-analysis") Traffic Analysis + h3(id="sec:traffic-analysis") Traffic analysis - p In the case of a visit of a very simple web page with a given URL, the browser sends a #[em request] to the web server configured for the domain specified in the URL. The web server, also called #[em host], sends then a #[em response] in the form of e.g. an HTML file that the browser downloads and displays. Most web pages nowadays are more complex and require the browser to send further requests to the same host (#[em first-party]) or even different hosts (potentially #[em third-party]) to download e.g. images, videos and fonts and to embed e.g. maps, tweets and comments. Please find more information about hosts and the distinction between first-party and third-party in the glossary in #[span.citation(data-cites="sec:glossary"): a(href="#sec:glossary") the Annex]. + p In the case of a visit to a very simple web page with a given URL (e.g. http://example.com/home.html), the browser sends a #[em request] to the web server configured for the domain specified in the URL (e.g. example.com). The web server, also called the #[em host], then sends a #[em response] in the form of, e.g. an HTML file (e.g. the home.html file), which the browser downloads and displays. Most web pages nowadays are more complex and include content such as images, videos, and fonts, or embed elements like maps, tweets, and comments. To assemble and show the whole web page, the browser sends further requests to the same host (#[em first-party]) or even different hosts (potentially #[em third-party]) to download the required content. A web page is often composed of dozens of elements, and due to the complexity of website architecture, website administrators are often not fully aware of all third parties involved in the functioning of their websites. - p The evidence collection tool extracted lists of distinct first-party, respectively third-party, hosts from the browser requests recorded as part of the traffic. Note that if a specific path is configured to be first-party, than requests to other paths may lead to the first-party host being also listed amongst the third-party hosts. + p The evidence collection tool extracted lists of distinct first- and third-party hosts from the browser requests recorded in each browsing session (with DNT signal set and without). These lists are presented below and aim to help by providing a comprehensive overview of all the hosts from which the browser requests elements. Note that subdomains (e.g. admin.example.com) of first-party domains (example.com) are, by default, considered third-party domains, whereas all URLs in the path (e.g. example.com/anysubpage) are treated as first-party by the automated evidence collection tool. More information about hosts and the distinction between first-party and third-party can be found in the glossary in the Annex: Glossary. - p A number of techniques allow hosts to track the browsing behaviour. The first-party host may instruct the browser to send requests for the (sole) purpose of providing information embedded in the request (e.g. cookies) to a given first-party or third-party host. Often, those requests are then responded with an empty file or with an image of size 1x1 pixel. Such files requested for the purpose of tracking are commonly called #[em web beacons]. + p A number of techniques allow hosts to track browsing behaviour. A first-party host may instruct the browser to send requests solely for the purpose of providing information embedded in the request (e.g. cookies) to a given first-party or third-party host. These requests are often responded to with an empty file or a 1x1 pixel image. Such files requested for tracking purposes are commonly referred to as #[em web beacons]. - p The evidence collection tool compares all requests to signature lists compiled to detect potential web beacons or otherwise problematic content. The positive matches with the lists #[a(href='https://easylist.to/#easyprivacy') EasyPrivacy] (#[code easyprivacy.txt]) and #[a(href='https://easylist.to/#fanboy-s-annoyance-list') Fanboy's Annoyance] (#[code fanboy-annoyance.txt]) from #[a(href="https://easylist.to") https://easylist.to] are presented in #[span.citation(data-cites="app:annex-beacons"): a(href="#app:annex-beacons") the Annex]. The list of #[em web beacon hosts] contains hosts of those requests that match the signature list EasyPrivacy. Note that the result may include false positives and may be incomplete due to inaccurate, outdated or incomplete signature lists. + p The evidence collection tool compares all requests against signature lists compiled to detect potential web beacons or annoyances such as in-page pop-ups. Positive matches with the lists #[a(href="https://easylist.to/#easyprivacy") EasyPrivacy] (#[code easyprivacy.txt]) and #[a(href="https://easylist.to/#fanboy-s-annoyance-list") Fanboy’s Annoyance] (#[code fanboy-annoyance.txt]) from #[a(href="https://easylist.to") https://easylist.to] are presented in the Annex: All potential web beacons. The list of #[em web beacon hosts] contains hosts of those requests that match the signature list EasyPrivacy. Note that the result may include false positives and may be incomplete due to inaccurate, outdated or incomplete signature lists. + + p #[em Cookies] are small text files stored on a user’s browser that allow websites to track and store information about the user’s interactions. However, they are limited in capacity and are transmitted with every HTTP request. #[em Local storage objects], on the other hand, offer a more modern method for websites to store larger amounts of data locally on a user’s browser, with better control over data access and expiration. Both cookies and local storage objects can be used for tracking purposes. p Eventually, the evidence collection tool logged all identified web forms that potentially transmit web form data using an unencrypted connection. - h4 First-Party Hosts - + h4 First-party hosts + ol each host in hosts.requests.firstParty li: a(href=`http://${host}`)= host - p Requests have been made to #{hosts.requests.firstParty.length} distinct first-party hosts. - - h4 Third-Party Hosts - + p Requests have been made to #{ hosts.requests.firstParty.length } distinct first-party host(s). + + h4 Third-party hosts + ol each host in hosts.requests.thirdParty li: a(href=`http://${host}`)= host - p Requests have been made to #{hosts.requests.thirdParty.length} distinct third-party hosts. + p Requests have been made to #{ hosts.requests.thirdParty.length } distinct third-party host(s). + + h4 First-party potential web beacon hosts - h4 First-Party Web Beacon Hosts - ol each host in hosts.beacons.firstParty li: a(href=`http://${host}`)= host if hosts.beacons.firstParty.length > 0 - p Potential first-party web beacons were sent to #{hosts.beacons.firstParty.length} distinct hosts. Corresponding HTTP requests for first- and third-parties are listed in #[span.citation(data-cites="app:annex-beacons"): a(href="#app:annex-beacons") the Annex]. + p Potential first-party web beacons were sent to #{ hosts.beacons.firstParty.length } distinct host(s). Corresponding HTTP requests for first- and third-parties are listed in the Annex: All potential web beacons. else - p No first-party web beacons were found. + p No first-party potential web beacons were found. + + h4 Third-party potential web beacon hosts - h4 Third-Party Web Beacon Hosts - ol each host in hosts.beacons.thirdParty li: a(href=`http://${host}`)= host - + if hosts.beacons.thirdParty.length > 0 - p Potential third-party web beacons were sent to #{hosts.beacons.thirdParty.length} distinct hosts. Corresponding HTTP requests for first- and third-parties are listed in #[span.citation(data-cites="app:annex-beacons"): a(href="#app:annex-beacons") the Annex]. + p Potential third-party web beacons were sent to #{ hosts.beacons.thirdParty.length } distinct host(s). Corresponding HTTP requests for first- and third-parties are listed in the Annex: All potential web beacons. else - p No third-party web beacons were found. + p No third-party potential web beacons were found. + + h4(id="sec:unsecure-forms") Web forms with non-encrypted transmission - h4(id="sec:unsecure-forms") Web Forms with non-encrypted Transmission - if unsafeForms.length > 0 table.unfase-webforms colgroup @@ -229,32 +275,32 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') thead tr th # - th Web Form ID + th Web form ID th Recipient URL - th HTTP Method + th HTTP method tbody - each form,index in unsafeForms + each form, index in unsafeForms tr - td= index+1 + td= index + 1 td= form.id td= form.action td= form.method - p The evidence collection tool logged #{unsafeForms.length} web forms that submit data potentially with no SSL encryption to a different web page. + p The evidence collection tool logged #{ unsafeForms.length } web forms that submit data potentially with no SSL encryption to a different web page. else p No web forms submitting data without SSL encryption were detected. - h3(id="sec:persistent-data-analysis") Persistent Data Analysis + h3(id="sec:persistent-data-analysis") Persistent data analysis + + p The evidence collection tool analysed cookies after the browsing session. Web pages can also use the persistent HTML5 #[em local storage]. The subsequent section lists its content after the browsing. + + - var cookiesByStorage = groupBy(cookies, "firstPartyStorage"); + + each cookieList, index in {'first-party': cookiesByStorage['true'] || [], 'third-party': cookiesByStorage['false'] || []} + h4 Cookies linked to #{ index } hosts - p The evidence collection tool analysed persistent cookies after the browsing session. Web pages can also use the persistent HTML5 #[em local storage]. #[span.citation(data-cites="sec:local-storage"): a(href="#sec:local-storage") The subsequent section] lists its content after the browsing. - - - var cookiesByStorage = groupBy(cookies, 'firstPartyStorage') - - each cookieList,index in {'First-Party': cookiesByStorage['true'] || [], 'Third-Party': cookiesByStorage['false'] || []} - h4 Cookies linked to #{index} Hosts - if cookieList.length > 0 - table.cookies(style="width: 100%;") + table.cookies(style="width: 100%") colgroup col(width="0%") col(width="0%") @@ -269,26 +315,32 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') th.trunc Name th.notrunc Expiry in days tbody - each cookie,index in cookieList + each cookie, index in cookieList tr - td.notrunc= index+1 - td.notrunc: a(href=`http://${cookie.domain}`)= cookie.domain - td.notrunc: a(href=`http://${cookie.domain}${cookie.path}`)= cookie.path - td.trunc= cookie.name + td.notrunc= index + 1 + td.notrunc: a( + href=`http://${cookie.domain}`, + title=`http://${cookie.domain}` + )= cookie.domain + td.notrunc: a( + href=`http://${cookie.domain}${cookie.path}`, + title=`http://${cookie.domain}${cookie.path}` + )= cookie.path + td.trunc(title=cookie.name)= cookie.name td.notrunc if cookie.session em session else = cookie.expiresDays - p In total, #{cookieList.length} #{index.toLowerCase()} cookies were found. + p In total, #{ cookieList.length } #{ index.toLowerCase() } cookie(s) were found. else - p No #{cookieList.length} #{index.toLowerCase()} cookies were found. + p No #{ cookieList.length } #{ index.toLowerCase() } cookies were found. + + h4(id="sec:local-storage") Local storage - h4(id="sec:local-storage") Local Storage - if Object.keys(localStorage).length > 0 - table.local-storage(style="width: 100%;") + table.local-storage(style="width: 100%") colgroup col(width="0%") col(width="20%") @@ -301,35 +353,35 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') th.trunc Key th.trunc Value tbody - - let index = 1 - each storage,url in localStorage - each data,key in storage + - let index = 1; + each storage, url in localStorage + each data, key in storage tr td.notrunc= index++ - td.trunc: a(href=url title=url)= url.replace(/(^\w+:|^)\/\//, '') - td.trunc= key + td.trunc: a(href=url, title=url)= url.replace(/(^\w+:|^)\/\//, "") + td.trunc(title=key)= key td.trunc.code: pre: code= JSON.stringify(data.value, null, 2) else p The local storage was found to be empty. h1(id="app:annex") Annex - - h2(id="app:history") Browsing History - - p For the collection of evidence, the browser navigated consecutively to the following #{browsing_history.length} webpage(s): - + + h2(id="app:history") Browsing history + + p For the collection of evidence, the browser navigated consecutively to the following #{ browsing_history.length } web page(s): + ol each link in browsing_history li: a(href=link)= link - - h2(id="app:annex-beacons") All Beacons - + + h2(id="app:annex-beacons") All potential web beacons + p The data transmitted by beacons using HTTP GET parameters are decoded for improved readability and displayed beneath the beacon URL. - + each beaconsByList, listName in groupBy(beacons, 'listName') h5(id=`annex-beacons-${listName}`)= listName - - table.adblock-findings(style="width: 100%;") + + table.adblock-findings(style="width: 100%") colgroup col(width="0%") col(width="100%") @@ -340,25 +392,25 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') th.trunc Sample URL th.notrunc Freq. tbody - each beacon,index in beacons + each beacon, index in beaconsByList tr - td.notrunc= index+1 + td.notrunc= index + 1 td.trunc(title=beacon.url)= beacon.url td.notrunc= beacon.occurrances if beacon.query tr td.notrunc - td.trunc.code(colspan=2): pre: code= JSON.stringify(beacon.query, null, 2).split("\n").slice(1,-1).join("\n").replace(/^ /mg , '') - + td.trunc.code(colspan=2): pre: code= JSON.stringify(beacon.query, null, 2).split("\n").slice(1, -1).join("\n").replace(/^ /gm, "") + if testSSL - - var results = testSSL.scanResult[0] - - h2(id="app:testssl") TestSSL Scan - - p The following data stems from a #[a(href="https://testssl.sh/") TestSSL] scan. The severity ratings are automatically computed by the TestSSL software without consideration of the specifics of the individual website. They do not reflect the opinions or views of the website evidence collector authors. - + - var results = testSSL.scanResult[0]; + + h2(id="app:testssl") TestSSL scan + + p The following data stems from a #[a(href="https://testssl.sh/") TestSSL] scan. The severity ratings are automatically computed by the TestSSL software without considering the security requirements of the individual website. They do not reflect the opinions or views of the website evidence collector's authors. + p.screen-only #[a(href="testssl/testssl.html") Click here] to check wether the full TestSSL scan report is available. - + table(width="100%") colgroup col @@ -371,16 +423,16 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td OpenSSL version td= testSSL.openssl tr - td Target Host - td #{results.targetHost} (#{results.ip}) - + td Target host + td #{ results.targetHost } (#{ results.ip }) + h3 Protocols - + table(width="100%") colgroup - col(style='width: 0%') - col(style='width: 100%') - col(style='width: 0%') + col(style="width: 0%") + col(style="width: 100%") + col(style="width: 0%") thead tr th Protocol @@ -392,15 +444,15 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td.notrunc= protocol.id td= protocol.finding td.notrunc= protocol.severity - - h3 HTTPS/SSL Vulnerabilities + + h3 HTTPS/SSL vulnerabilities table(width="100%") colgroup - col(style='width: 0%') - col(style='width: 80%') - col(style='width: 20%') - col(style='width: 0%') + col(style="width: 0%") + col(style="width: 80%") + col(style="width: 20%") + col(style="width: 0%") thead tr th Vulnerability @@ -412,21 +464,23 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') tr td.notrunc= vulnerability.id td.trunc(title=vulnerability.finding)= vulnerability.finding - td.trunc(title=vulnerability.cve).trunc + td.trunc.trunc(title=vulnerability.cve) if vulnerability.cve each cve in vulnerability.cve.split(' ') - a(href=`https://cve.mitre.org/cgi-bin/cvename.cgi?name=${cve}`)= cve + a( + href=`https://cve.mitre.org/cgi-bin/cvename.cgi?name=${cve}` + )= cve | td.notrunc= vulnerability.severity - - h3 Cipher Categories - - table(style='width: 100%') + + h3 Cipher categories + + table(style="width: 100%") colgroup - col(style='width: 0%') - col(style='width: 100%') - col(style='width: 0%') - col(style='width: 0%') + col(style="width: 0%") + col(style="width: 100%") + col(style="width: 0%") + col(style="width: 0%") thead tr th Name @@ -440,16 +494,18 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td.notrunc= cipher.finding td.notrunc if cipher.cwe - a(href=`https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=${cipher.cwe.replace('CWE-','')}`)= cipher.cwe + a( + href=`https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=${cipher.cwe.replace('CWE-','')}` + )= cipher.cwe td.notrunc= cipher.severity - - h3 HTTP Header Responses - table(style='width: 100%') + h3 HTTP header responses + + table(style="width: 100%") colgroup - col(style='width: 0%') - col(style='width: 100%') - col(style='width: 0%') + col(style="width: 0%") + col(style="width: 100%") + col(style="width: 0%") thead tr th Name @@ -461,27 +517,26 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td.notrunc= response.id td.trunc(title=response.finding)= response.finding td.notrunc= response.severity - - h2(id='app:glossary') Glossary + + h2(id="app:glossary") Glossary dl - dt Filter Lists - dd Browser extensions commonly referred by #[em Adblocker] have been developed to block the loading of advertisements based on filter lists. Later on, filter lists have been extended to block also the loading of web page elements connected to the tracking of web page visitors. For this evidence collection, publicly available tracking filter lists are re-purposed to identify web page elements that may track the web page visitors. dt Do Not Track (DNT for short, HTTP) - dd The Do Not Track header is the proposed HTTP header field DNT that requests that a web service does not track its individual visitors. Note that this request cannot be enforced by technical means on the visitors’ side. It is upon the web service to take the DNT header field into account. For this evidence collection, the Do Not Track header is not employed. + dd The Do Not Track header is the proposed HTTP header field DNT, which requests that a web service does not track its individual visitors. Note that this request cannot be enforced by technical means on the visitors’ side. It is upon the web service to take the DNT header field into account. + dt Filter Lists + dd Browser extensions commonly referred to as Adblockers have been developed to block the loading of advertisements based on filter lists. Over time, these filter lists have been extended to also block the loading of web page elements associated with tracking web page visitors. For this evidence collection, publicly available tracking filter lists are used to identify web page elements that may track the web page visitors. dt First-Party - dd In this document, #[em first-party] is a classification of the resources links, web beacons, and cookies. To be first party, the resource domain must match the domain of the inspected web service or other configured first-party domains. Note that the resource path must also be within the path of the web service to be considered first-party. + dd In this document, first-party is a classification for resource links, web beacons, and cookies. To be considered first party, the resource’s domain must match the domain of the inspected web service or other configured first-party domains. Note that the resource path must also be within the path of the web service to be classified as first-party. dt Host (HTTP) - dd The HTTP #[em host] is the computer receiving and answering browser requests for web pages. + dd The HTTP host is the computer that receives and responds to browser requests for web pages. + dt Local Storage (HTML5) + dd Most web browsers allow web pages to store data locally in the browser profile. This local storage is specific to the website and persists through browser shutdowns. As embedded third-party resources may also have access to first-party local storage, it is classified both as first- and third-party. dt Redirect (HTTP) - dd A request for a web page may be answered with a new location (URL) to be requested instead. These HTTP #[em redirects] can be used to enforce the use of HTTPS. Visitors requested an HTTP web page are redirected to the corresponding HTTPS web page. + dd A request for a web page may be answered with a new location (URL) to be requested instead. These HTTP redirects can be used to enforce the use of HTTPS. When visitors request an HTTP web page, they are redirected to the corresponding HTTPS web page. dt Request (HTTP) - dd To download and display a web page identified by an URL, browsers send HTTP #[em requests] with the URL to the host computer specified as part of the URL. - dt Local Storage (HTML5) - dd Modern web browsers allow web pages to store data locally in the browser profile. This #[em local storage] is web site-specific and persistent through browser shutdowns. As embedded third-party resources may also have access to the first-party local storage, it is classified both as first- and third-party. + dd To download and display a web page identified by a URL, browsers send HTTP requests with the URL to the host computer specified as part of the URL. dt Third-Party - dd Links, web beacons and cookies that are not #[em first-party] (see above) are classified as #[em third-party]. + dd Links, web beacons and cookies that are not first-party (see above) are classified as third-party. dt Web Beacon - dd A web beacon is one of various techniques used on web pages to unobtrusively (usually invisibly) allow tracking of web page visitors. A web beacon can be implemented for instance as a 1x1 pixel image, a transparent image, or an empty file that is requested together with other resources when a web page is loaded. + dd A web beacon is one of various techniques used on web pages to unobtrusively (usually invisibly) track web page visitors. A web beacon can be implemented as a 1x1 pixel image, a transparent image, or an empty file requested alongside other resources when a web page is loaded. dt Web Beacon Host - dd The #[em host] in the URL of a #[em request] of a #[em Web Beacon] is called #[em Web Beacon host]. - + dd The host in the URL of a request of a web beacon is referred to as the web beacon host. diff --git a/src/assets/template.pug b/src/assets/template.pug index 1ac85da339d87b2f0063b626ea8b624851005dd1..a4ef6adca4e17814a407e48c1db11d8d5adb85dd 100644 --- a/src/assets/template.pug +++ b/src/assets/template.pug @@ -1,18 +1,31 @@ doctype html -html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') +html(xmlns="http://www.w3.org/1999/xhtml", xml:lang="en", lang="en") head - meta(charset='utf-8') - meta(name='generator', content='website-evidence-collector') - meta(name='viewport', content='width=device-width, initial-scale=1.0, user-scalable=yes') - title #{title} (#{uri_ins}) + meta(charset="utf-8") + meta(name="generator", content="website-evidence-collector") + meta( + name="viewport", + content="width=device-width, initial-scale=1.0, user-scalable=yes" + ) + title #{ title } (#{ uri_ins }) base(target="_blank")/ - style(type='text/css') !{inlineCSS} - style(type='text/css'). + style(type="text/css") !{ inlineCSS } + style(type="text/css"). /* pandoc */ - code{white-space: pre-wrap;} - span.smallcaps{font-variant: small-caps;} - span.underline{text-decoration: underline;} - div.column{display: inline-block; vertical-align: top; width: 50%;} + code { + white-space: pre-wrap; + } + span.smallcaps { + font-variant: small-caps; + } + span.underline { + text-decoration: underline; + } + div.column { + display: inline-block; + vertical-align: top; + width: 50%; + } /* github */ .markdown-body > article { @@ -59,24 +72,26 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') counter-increment: h3counter; } - h1.nocount:before, h2.nocount:before, h3.nocount:before { - content: none; - counter-increment: none; + h1.nocount:before, + h2.nocount:before, + h3.nocount:before { + content: none; + counter-increment: none; } /* annex */ - h1[id^=app]:before { + h1[id^="app"]:before { content: none; } - h1[id^=app] { + h1[id^="app"] { counter-reset: h2counter; } - h2[id^=app]:before { + h2[id^="app"]:before { content: counter(h2counter, upper-alpha) "\0000a0\0000a0"; } - #logo{ + #logo { width: 40%; float: right; background-color: var(--color-canvas-default); @@ -88,9 +103,9 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') .trunc { white-space: nowrap; - text-overflow:ellipsis; + text-overflow: ellipsis; overflow: hidden; - max-width:1px; + max-width: 1px; } .markdown-body table td.code { @@ -101,13 +116,20 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') margin: 0px; } - td.highlighted, td.highlighted pre, li.highlighted a { + td.highlighted, + td.highlighted pre, + li.highlighted a { background-color: red; color: white; } @media print { - .markdown-body h1,h2,h3,h4,h5,h6 { + .markdown-body h1, + h2, + h3, + h4, + h5, + h6 { break-after: avoid-page; } @@ -116,72 +138,88 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') } } + .unnumbered::before { + content: none !important; + counter-increment: none; + } + body.markdown-body: article header#title-block-header - div#logo + #logo include /wec_logo.svg - h1.title= title - h2.subtitle: a(href=uri_ins)= uri_ins + h1.title.unnumbered= title + h2.subtitle.unnumbered: a(href=uri_ins)= uri_ins - h1(id="sec:evidence-collection-organisation") Evidence Collection Organisation + h1(id="sec:evidence-collection-organisation") Evidence collection organisation table colgroup - col(style='width: 50%') - col(style='width: 50%') + col(style="width: 50%") + col(style="width: 50%") tbody tr - td Target Web Service + td Target web service td: code= uri_ins tr - td Automated Evidence Collection Start Time - td= new Date(start_time).toLocaleString() + td Automated evidence collection start time + td= new Date(start_time).toLocaleString("en-GB") tr - td Automated Evidence Collection End Time - td= new Date(end_time).toLocaleString() + td Automated evidence collection end time + td= new Date(end_time).toLocaleString("en-GB") tr - td Software Version + td Software version td= script.version.commit || script.version.npm - tr - td Software Host - td= script['host'] - h1(id="sec:automated-evidence-collection") Automated Evidence Collection + h1(id="sec:automated-evidence-collection") Automated evidence collection + + p The automated evidence collection is carried out using the tool #[a(href="https://edps.europa.eu/press-publications/edps-inspection-software_en") website evidence collector], WEC (also #[a(href="https://code.europa.eu/EDPS/website-evidence-collector") on Code Europa EU]) in version #{ script.version.commit || script.version.npm } on the platform #{ browser.platform.name } in version #{ browser.platform.version }. The tool employs the browser #{ browser.name } in version #{ browser.version } for browsing the website. - p The automated evidence collection is carried out using the tool #[a(href="https://edps.europa.eu/press-publications/edps-inspection-software_en") website evidence collector] (also #[a(href="https://github.com/EU-EDPS/website-evidence-collector") on Github]) in version #{script.version.commit || script.version.npm} on the platform #{browser.platform.name} in version #{browser.platform.version}. The tool employs the browser #{browser.name} in version #{browser.version} for browsing the website. + p The evidence collection tool simulates a browsing session of the web service, capturing traffic between the browser and the Internet, along with any persistent data stored in the browser. While browsing, the tool gathers evidence and performs a number of checks. - p During the browsing, the tool gathers evidence and runs a number of checks. It takes screenshots from the browser to identify potential cookie banners. It tests the use of HTTPS/SSL to check whether the website enforces a HTTPS connection. Then, the evidence collection tool scans the first web page for links to common social media and collaboration platforms for statistics on the overall use of potentially privacy-intrusive third-party web services. + p It captures screenshots from the browser to identify potential cookie banners. It also tests HTTPS/SSL usage to determine whether the website enforces a secure connection. Then, the evidence collection tool scans the first web page for links to common social media and collaboration platforms, gathering data on the overall use of potentially privacy-intrusive third-party web services. - p The analysis of the recorded traffic between the browser and both the target web service as well as involved third-party web services, and the browser’s persistent storage follows in a #[span.citation(data-cites="sec:traffic-and-persistent-data-analysis"): a(target="_parent" href="#traffic-and-persistent-data-analysis") subsequent section]. + p The recorded traffic between the browser, the target web service, and involved third-party web services, as well as the browser’s persistent storage, will be analysed in a #[span.citation(data-cites="sec:traffic-and-persistent-data-analysis"): a(href="#traffic-and-persistent-data-analysis") subsequent section]. - h2(id="sec:webpage-visit") Webpage Visit + p Generally, the tool browses a random subset of the target web service pages starting from the initial web page. However, the browsing can also include a set of predefined web pages. The exhaustive list of browsed web pages for this specific evidence collection is given in #[span.citation(data-cites="app:history"): a(href="#app:history") the Annex: Browsing history]. + + h2(id="sec:webpage-visit") Web page visit p - | On #{new Date(start_time).toLocaleString()}, the evidence collection tool navigated the browser to #[a(href=uri_ins)= uri_ins]. The final location after potential redirects was #[a(href=uri_dest)= uri_dest]. + | On #{ new Date(start_time).toLocaleString("en-GB") }, the evidence collection tool navigated the browser to #[a(href=uri_ins)= uri_ins]. The final location after potential redirects was #[a(href=uri_dest)= uri_dest]. if script.config.screenshots | - | The evidence collection tool took two screenshots #[span.citation(data-cites="fig:screenshot-top") to cover the top of the webpage] and #[span.citation(data-cites="fig:screenshot-bottom") the bottom]. + | The evidence collection tool took two screenshots #[span.citation(data-cites="fig:screenshot-top") to cover the top of the web page] and #[span.citation(data-cites="fig:screenshot-bottom") the bottom]. if script.config.screenshots figure - img(src=`data:image/png;base64,${screenshots.screenshot_top}` alt="Webpage Top Screenshot" id="fig:screenshot-top" style="width:100.0%") - figcaption Webpage Top Screenshot + img(id="fig:screenshot-top", + src=`data:image/png;base64,${screenshots.screenshot_top}`, + alt="Web page top screenshot", + style="width: 100%" + ) + figcaption Web page top screenshot figure - img(src=`data:image/png;base64,${screenshots.screenshot_bottom}` alt="Webpage Bottom Screenshot" id="fig:screenshot-bottom" style="width:100.0%") - figcaption Webpage Bottom Screenshot + img(id="fig:screenshot-bottom", + src=`data:image/png;base64,${screenshots.screenshot_bottom}`, + alt="Web page bottom screenshot", + style="width: 100%" + ) + figcaption Web page bottom screenshot h2(id="sec:use-of-httpsssl") Use of HTTPS/SSL - p The evidence collection tool assessed the redirecting behaviour of #{host} with respect to the use of HTTPS. + p HTTP (Hypertext Transfer Protocol) is a communication standard that transmits data between a website and a user’s browser in an unencrypted format, making it vulnerable to interception and eavesdropping. In contrast, HTTPS (Hypertext Transfer Protocol Secure) extends HTTP by adding an extra layer of security through encryption, which protects the confidentiality and integrity of the data exchanged between a website and a user’s browser. + + p The evidence collection tool assessed the behaviour of #{ host } with respect to the use of HTTPS. table.use-of-httpsssl colgroup - col(style='width: 50%') - col(style='width: 50%') + col(style="width: 50%") + col(style="width: 50%") tbody tr - td allows connection with HTTPS + td Allows connection with HTTPS td= secure_connection.https_support tr td HTTP redirect to HTTPS @@ -203,7 +241,7 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td= secure_connection.https_error if testSSL && testSSL.scanResult[0] - - var results = testSSL.scanResult[0] + - var results = testSSL.scanResult[0]; - sortSeverity = function(a,b) { @@ -219,57 +257,75 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') return severityToNumber[a.severity]-severityToNumber[b.severity]; } - p The software TestSSL from #[a(href="https://testssl.sh") https://testssl.sh] inspected the HTTPS configuration of the web service host #{results.targetHost}. It classifies detected vulnerabilities by their level of severity #[em low], #[em medium], #[em high], or #[em critical]. The severity ratings are automatically computed by the TestSSL software without consideration of the specifics of the individual website. They do not reflect the opinions or views of the website evidence collector authors. Details on the findings are listed in #[span.citation(data-cites="app:testssl"): a(target="_parent" href="#app:testssl") the Annex]. + p The software TestSSL from #[a(href="https://testssl.sh") https://testssl.sh] inspected the HTTPS configuration of the web service host #{ results.targetHost }. It classifies detected vulnerabilities by their level of severity #[em low], #[em medium], #[em high], or #[em critical]. The severity ratings are automatically computed by the TestSSL software without considering the security requirements of the individual website. They do not reflect the opinions or views of the website evidence collector's authors. Details of the findings are listed in #[span.citation(data-cites="app:testssl"): a(target="_parent", href="#app:testssl") the Annex: TestSSL scan]. table.testssl-summary thead tr - th HTTPS/SSL Vulnerabilities per Severity + th HTTPS/SSL vulnerabilities per severity th.notrunc Freq. tbody - - var vulnerabilitiesBySeverity = groupBy(results.vulnerabilities,'severity'); + - var vulnerabilitiesBySeverity = groupBy(results.vulnerabilities, "severity"); tr td Critical - td.notrunc= vulnerabilitiesBySeverity['CRITCAL'] ? vulnerabilitiesBySeverity['CRITCAL'].length : 0 + td.notrunc= vulnerabilitiesBySeverity["CRITCAL"] ? vulnerabilitiesBySeverity["CRITCAL"].length : 0 tr td High - td.notrunc= vulnerabilitiesBySeverity['HIGH'] ? vulnerabilitiesBySeverity['HIGH'].length : 0 + td.notrunc= vulnerabilitiesBySeverity["HIGH"] ? vulnerabilitiesBySeverity["HIGH"].length : 0 tr td Medium - td.notrunc= vulnerabilitiesBySeverity['MEDIUM'] ? vulnerabilitiesBySeverity['MEDIUM'].length : 0 + td.notrunc= vulnerabilitiesBySeverity["MEDIUM"] ? vulnerabilitiesBySeverity["MEDIUM"].length : 0 tr td Low - td.notrunc= vulnerabilitiesBySeverity['LOW'] ? vulnerabilitiesBySeverity['LOW'].length : 0 + td.notrunc= vulnerabilitiesBySeverity["LOW"] ? vulnerabilitiesBySeverity["LOW"].length : 0 + h2(id="sec:use-of-csp") Use of content security policies (CSPs) - h2(id="sec:use-of-csp") Use of Content Security Policies (CSPs) + p Upon a browser's request for a web page, websites can specify a whitelist of mechanisms, domains, and subdomains in the #[a(href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP") Content Security Policy] (CSP) metadata sent along with the requested page. Browsers must respect this whitelist when embedding components such as styles, fonts, beacons, videos, and maps. - p Upon browser request of a web page, websites can indicate in the #[a(href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP") Content Security Policy] (CSP) meta data sent along with the requested web page a whitelist of mechanisms, domains and subdomains that browsers must respect when embedding components, such as styles, fonts, beacons, videos, maps, etc. + if hosts.contentSecurityPolicy.firstParty.length === 0 && hosts.contentSecurityPolicy.thirdParty.length === 0 + p No CSP metadata was found. Consequently, no restrictions apply. - if hosts.contentSecurityPolicy.firstParty.length > 0 - ol - each host in hosts.contentSecurityPolicy.firstParty + else + if hosts.contentSecurityPolicy.firstParty.length > 0 + ol // check if host looks like a host (instead of e.g blob: data: etc.) - if host.match(/[^\.]+\.[^\.]+/) + each host in hosts.contentSecurityPolicy.firstParty + if host.match(/[^\.]+\.[^\.]+/) + li: a(href=`http://${host}`)= host + else + li= host + + p The website has whitelisted #{ hosts.contentSecurityPolicy.firstParty.length } first-party domains and mechanisms. + else + p No CSP metadata related to first-party URLs was found. + + if hosts.contentSecurityPolicy.thirdParty.length > 0 + h4 Third-party content security policy hosts + + ol + each host in hosts.contentSecurityPolicy.thirdParty li: a(href=`http://${host}`)= host - else - li= host - p The website has whitelisted #{hosts.contentSecurityPolicy.firstParty.length} first-party domains and mechanisms. - else - p No CSP meta data was found. Consequently, no restrictions apply. + p The website has whitelisted #{ hosts.contentSecurityPolicy.thirdParty.length } distinct third-party host(s). + else + p No third-party content security policy hosts were whitelisted. + + h2(id="sec:use-of-social-media") Use of social media and collaboration platforms - h2(id="sec:use-of-social-media") Use of Social Media and Collaboration Platforms + p The website evidence collection tool found links from #[a(href=uri_dest)= uri_dest] to the following common social media and collaboration platforms. if links.social.length > 0 - table.use-of-social-media-and-collaboration-platforms(style="width: 100%") + table.use-of-social-media-and-collaboration-platforms( + style="width: 100%" + ) colgroup - col(style='width: 100%') - col(style='width: 100%') + col(style="width: 100%") + col(style="width: 100%") thead tr th Link URL - th Link Caption + th Link caption tbody each social in links.social tr @@ -278,88 +334,73 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') else p No corresponding links were found. - p Common social media and collaboration platforms linked from #[a(href=uri_dest)= uri_dest] have been considered. + h2#traffic-and-persistent-data-analysis Traffic and persistent data analysis - h2(id="traffic-and-persistent-data-analysis") Traffic and Persistent Data Analysis + p First, the browser visited #[a(href=uri_dest)= uri_dest]. The evidence collection navigated and collected evidence from #{ browsing_history.length > 1 ? browsing_history.length - 1 : "no" } additional web service page(s). - p The evidence collection tool simulates a browsing session of the web service to analyse hereafter the recorded traffic between the browser and the Internet as well as the persistent data stored in the browser. First, the browser visited #[a(href=uri_dest)= uri_dest]. The evidence collection took #{browsing_history.length > 1 ? browsing_history.length - 1 : "no"} other web page(s) into account. Generally, predefined pages and a random subset of all first-party link targets (URLs) from the initial web page #[a(href=uri_dest)= uri_dest] are considered. The exhaustive list of browsed web pages is given in #[span.citation(data-cites="app:history"): a(target="_parent" href="#app:history") the Annex]. + p The web page(s) were browsed consecutively between #{ new Date(start_time).toLocaleString("en-GB") } and #{ new Date(end_time).toLocaleString("en-GB") }. - p The web page(s) were browsed consecutively between #{new Date(start_time).toLocaleString()} and #{new Date(end_time).toLocaleString()}. + p During the browsing, the HTTP Header #[a(href="https://en.wikipedia.org/wiki/Do_Not_Track") Do Not Track] was #{ browser.extra_headers.dnt ? 'set' : 'not set' }. - p During the browsing, the HTTP Header #[a(href="https://en.wikipedia.org/wiki/Do_Not_Track") Do Not Track] was #{browser.extra_headers.dnt ? 'set' : 'not set'}. - - p For the subsequent analysis, the following hosts (with their path) were defined as first-party: + p For the subsequent analysis, the following URLs (hosts with their paths) were defined as first-party: ol each uri in uri_refs - li: a(href=uri)= uri.replace(/(^\w+:|^)\/\//, '') + li: a(href=uri)= uri.replace(/(^\w+:|^)\/\//, "") + + h3(id="sec:traffic-analysis") Traffic analysis - h3(id="sec:traffic-analysis") Traffic Analysis + p In the case of a visit to a very simple web page with a given URL (e.g. http://example.com/home.html), the browser sends a #[em request] to the web server configured for the domain specified in the URL (e.g. example.com). The web server, also called the #[em host], then sends a #[em response] in the form of, e.g. an HTML file (e.g. the home.html file), which the browser downloads and displays. Most web pages nowadays are more complex and include content such as images, videos, and fonts, or embed elements like maps, tweets, and comments. To assemble and show the whole web page, the browser sends further requests to the same host (#[em first-party]) or even different hosts (potentially #[em third-party]) to download the required content. A web page is often composed of dozens of elements, and due to the complexity of website architecture, website administrators are often not fully aware of all third parties involved in the functioning of their websites. - p In the case of a visit of a very simple web page with a given URL, the browser sends a #[em request] to the web server configured for the domain specified in the URL. The web server, also called #[em host], sends then a #[em response] in the form of e.g. an HTML file that the browser downloads and displays. Most web pages nowadays are more complex and require the browser to send further requests to the same host (#[em first-party]) or even different hosts (potentially #[em third-party]) to download e.g. images, videos and fonts and to embed e.g. maps, tweets and comments. Please find more information about hosts and the distinction between first-party and third-party in the glossary in #[span.citation(data-cites="sec:glossary"): a(target="_parent" href="#sec:glossary") the Annex]. + p The evidence collection tool extracted lists of distinct first- and third-party hosts from the browser requests recorded in each browsing session (with DNT signal set and without). These lists are presented below and aim to help by providing a comprehensive overview of all the hosts from which the browser requests elements. Note that subdomains (e.g. admin.example.com) of first-party domains (example.com) are, by default, considered third-party domains, whereas all URLs in the path (e.g. example.com/anysubpage) are treated as first-party by the automated evidence collection tool. More information about hosts and the distinction between first-party and third-party can be found in the glossary in #[span.citation(data-cites="app:glossary"): a(href="#app:glossary") the Annex: Glossary]. - p The evidence collection tool extracted lists of distinct first-party, respectively third-party, hosts from the browser requests recorded as part of the traffic. Note that if a specific path is configured to be first-party, than requests to other paths may lead to the first-party host being also listed amongst the third-party hosts. + p A number of techniques allow hosts to track browsing behaviour. A first-party host may instruct the browser to send requests solely for the purpose of providing information embedded in the request (e.g. cookies) to a given first-party or third-party host. These requests are often responded to with an empty file or a 1x1 pixel image. Such files requested for tracking purposes are commonly referred to as #[em web beacons]. - p A number of techniques allow hosts to track the browsing behaviour. The first-party host may instruct the browser to send requests for the (sole) purpose of providing information embedded in the request (e.g. cookies) to a given first-party or third-party host. Often, those requests are then responded with an empty file or with an image of size 1x1 pixel. Such files requested for the purpose of tracking are commonly called #[em web beacons]. + p The evidence collection tool compares all requests against signature lists compiled to detect potential web beacons or annoyances such as in-page pop-ups. Positive matches with the lists #[a(href="https://easylist.to/#easyprivacy") EasyPrivacy] (#[code easyprivacy.txt]) and #[a(href="https://easylist.to/#fanboy-s-annoyance-list") Fanboy’s Annoyance] (#[code fanboy-annoyance.txt]) from #[a(href="https://easylist.to") https://easylist.to] are presented in #[span.citation(data-cites="app:annex-beacons"): a(href="#app:annex-beacons") the Annex: All potential web beacons]. The list of #[em web beacon hosts] contains hosts of those requests that match the signature list EasyPrivacy. Note that the result may include false positives and may be incomplete due to inaccurate, outdated or incomplete signature lists. - p The evidence collection tool compares all requests to signature lists compiled to detect potential web beacons or otherwise problematic content. The positive matches with the lists #[a(href='https://easylist.to/#easyprivacy') EasyPrivacy] (#[code easyprivacy.txt]) and #[a(href='https://easylist.to/#fanboy-s-annoyance-list') Fanboy's Annoyance] (#[code fanboy-annoyance.txt]) from #[a(href="https://easylist.to") https://easylist.to] are presented in #[span.citation(data-cites="app:annex-beacons"): a(target="_parent" href="#app:annex-beacons") the Annex]. The list of #[em web beacon hosts] contains hosts of those requests that match the signature list EasyPrivacy. Note that the result may include false positives and may be incomplete due to inaccurate, outdated or incomplete signature lists. + p #[em Cookies] are small text files stored on a user’s browser that allow websites to track and store information about the user’s interactions. However, they are limited in capacity and are transmitted with every HTTP request. #[em Local storage objects], on the other hand, offer a more modern method for websites to store larger amounts of data locally on a user’s browser, with better control over data access and expiration. Both cookies and local storage objects can be used for tracking purposes. p Eventually, the evidence collection tool logged all identified web forms that potentially transmit web form data using an unencrypted connection. - h4 First-Party Hosts + h4 First-party hosts ol each host in hosts.requests.firstParty li: a(href=`http://${host}`)= host - p Requests have been made to #{hosts.requests.firstParty.length} distinct first-party hosts. + p Requests have been made to #{ hosts.requests.firstParty.length } distinct first-party host(s). - h4 Third-Party Hosts + h4 Third-party hosts ol each host in hosts.requests.thirdParty li: a(href=`http://${host}`)= host - p Requests have been made to #{hosts.requests.thirdParty.length} distinct third-party hosts. + p Requests have been made to #{ hosts.requests.thirdParty.length } distinct third-party host(s). - h4 First-Party Web Beacon Hosts + h4 First-party potential web beacon hosts ol each host in hosts.beacons.firstParty li: a(href=`http://${host}`)= host if hosts.beacons.firstParty.length > 0 - p Potential first-party web beacons were sent to #{hosts.beacons.firstParty.length} distinct hosts. Corresponding HTTP requests for first- and third-parties are listed in #[span.citation(data-cites="app:annex-beacons"): a(target="_parent" href="#app:annex-beacons") the Annex]. + p Potential first-party web beacons were sent to #{ hosts.beacons.firstParty.length } distinct host(s). Corresponding HTTP requests for first- and third-parties are listed in #[span.citation(data-cites="app:annex-beacons"): a(target="_parent", href="#app:annex-beacons") the Annex: All potential web beacons]. else - p No first-party web beacons were found. + p No first-party potential web beacons were found. - h4 Third-Party Web Beacon Hosts + h4 Third-party potential web beacon hosts ol each host in hosts.beacons.thirdParty li: a(href=`http://${host}`)= host if hosts.beacons.thirdParty.length > 0 - p Potential third-party web beacons were sent to #{hosts.beacons.thirdParty.length} distinct hosts. Corresponding HTTP requests for first- and third-parties are listed in #[span.citation(data-cites="app:annex-beacons"): a(target="_parent" href="#app:annex-beacons") the Annex]. + p Potential third-party web beacons were sent to #{ hosts.beacons.thirdParty.length } distinct host(s). Corresponding HTTP requests for first- and third-parties are listed in #[span.citation(data-cites="app:annex-beacons"): a(target="_parent", href="#app:annex-beacons") the Annex: All potential web beacons]. else - p No third-party web beacons were found. - - if hosts.contentSecurityPolicy.firstParty.length > 0 - - h4 Third-Party Content Security Policy Hosts - - p Upon browser request of a web page, websites can indicate in the #[a(href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP") Content Security Policy] (CSP) meta data sent along with the requested web page a whitelist of mechanisms, domains and subdomains that browsers must respect when embedding components, such as styles, fonts, beacons, videos, maps, etc. - - ol - each host in hosts.contentSecurityPolicy.thirdParty - li: a(href=`http://${host}`)= host + p No third-party potential web beacons were found. - if hosts.contentSecurityPolicy.thirdParty.length > 0 - p The website has whitelisted #{hosts.contentSecurityPolicy.thirdParty.length} distinct third-party hosts. - else - p No third-party content security policy hosts were whitelisted. - - h4(id="sec:unsecure-forms") Web Forms with non-encrypted Transmission + h4(id="sec:unsecure-forms") Web forms with non-encrypted transmission if unsafeForms.length > 0 table.unfase-webforms @@ -369,32 +410,32 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') thead tr th # - th Web Form ID + th Web form ID th Recipient URL - th HTTP Method + th HTTP method tbody - each form,index in unsafeForms + each form, index in unsafeForms tr - td= index+1 + td= index + 1 td= form.id td= form.action td= form.method - p The evidence collection tool logged #{unsafeForms.length} web forms that submit data potentially with no SSL encryption to a different web page. + p The evidence collection tool logged #{ unsafeForms.length } web forms that submit data potentially with no SSL encryption to a different web page. else p No web forms submitting data without SSL encryption were detected. - h3(id="sec:persistent-data-analysis") Persistent Data Analysis + h3(id="sec:persistent-data-analysis") Persistent data analysis - p The evidence collection tool analysed persistent cookies after the browsing session. Web pages can also use the persistent HTML5 #[em local storage]. #[span.citation(data-cites="sec:local-storage"): a(target="_parent" href="#sec:local-storage") The subsequent section] lists its content after the browsing. + p The evidence collection tool analysed cookies after the browsing session. Web pages can also use the persistent HTML5 #[em local storage]. #[span.citation(data-cites="sec:local-storage"): a(target="_parent", href="#sec:local-storage") The subsequent section] lists its content after the browsing. - - var cookiesByStorage = groupBy(cookies, 'firstPartyStorage') + - var cookiesByStorage = groupBy(cookies, "firstPartyStorage"); - each cookieList,index in {'First-Party': cookiesByStorage['true'] || [], 'Third-Party': cookiesByStorage['false'] || []} - h4 Cookies linked to #{index} Hosts + each cookieList, index in {'first-party': cookiesByStorage['true'] || [], 'third-party': cookiesByStorage['false'] || []} + h4 Cookies linked to #{ index } hosts if cookieList.length > 0 - table.cookies(style="width: 100%;") + table.cookies(style="width: 100%") colgroup col(width="0%") col(width="0%") @@ -409,26 +450,32 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') th.trunc Name th.notrunc Expiry in days tbody - each cookie,index in cookieList + each cookie, index in cookieList tr - td.notrunc= index+1 - td.notrunc: a(href=`http://${cookie.domain}`)= cookie.domain - td.notrunc: a(href=`http://${cookie.domain}${cookie.path}`)= cookie.path - td.trunc= cookie.name + td.notrunc= index + 1 + td.notrunc: a( + href=`http://${cookie.domain}`, + title=`http://${cookie.domain}` + )= cookie.domain + td.notrunc: a( + href=`http://${cookie.domain}${cookie.path}`, + title=`http://${cookie.domain}${cookie.path}` + )= cookie.path + td.trunc(title=cookie.name)= cookie.name td.notrunc if cookie.session em session else = cookie.expiresDays - p In total, #{cookieList.length} #{index.toLowerCase()} cookies were found. + p In total, #{ cookieList.length } #{ index.toLowerCase() } cookie(s) were found. else - p No #{cookieList.length} #{index.toLowerCase()} cookies were found. + p No #{ index.toLowerCase() } cookies were found. - h4(id="sec:local-storage") Local Storage + h4(id="sec:local-storage") Local storage if Object.keys(localStorage).length > 0 - table.local-storage(style="width: 100%;") + table.local-storage(style="width: 100%") colgroup col(width="0%") col(width="20%") @@ -441,35 +488,35 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') th.trunc Key th.trunc Value tbody - - let index = 1 - each storage,url in localStorage - each data,key in storage + - let index = 1; + each storage, url in localStorage + each data, key in storage tr td.notrunc= index++ - td.trunc: a(href=url title=url)= url.replace(/(^\w+:|^)\/\//, '') - td.trunc= key + td.trunc: a(href=url, title=url)= url.replace(/(^\w+:|^)\/\//, "") + td.trunc(title=key)= key td.trunc.code: pre: code= JSON.stringify(data.value, null, 2) else p The local storage was found to be empty. h1(id="app:annex") Annex - h2(id="app:history") Browsing History + h2(id="app:history") Browsing history - p For the collection of evidence, the browser navigated consecutively to the following #{browsing_history.length} webpage(s): + p For the collection of evidence, the browser navigated consecutively to the following #{ browsing_history.length } web page(s): ol each link in browsing_history li: a(href=link)= link - h2(id="app:annex-beacons") All Beacons + h2(id="app:annex-beacons") All potential web beacons p The data transmitted by beacons using HTTP GET parameters are decoded for improved readability and displayed beneath the beacon URL. each beaconsByList, listName in groupBy(beacons, 'listName') h5(id=`annex-beacons-${listName}`)= listName - table.adblock-findings(style="width: 100%;") + table.adblock-findings(style="width: 100%") colgroup col(width="0%") col(width="100%") @@ -480,24 +527,24 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') th.trunc Sample URL th.notrunc Freq. tbody - each beacon,index in beacons + each beacon, index in beaconsByList tr - td.notrunc= index+1 + td.notrunc= index + 1 td.trunc(title=beacon.url)= beacon.url td.notrunc= beacon.occurrances if beacon.query tr td.notrunc - td.trunc.code(colspan=2): pre: code= JSON.stringify(beacon.query, null, 2).split("\n").slice(1,-1).join("\n").replace(/^ /mg , '') + td.trunc.code(colspan=2): pre: code= JSON.stringify(beacon.query, null, 2).split("\n").slice(1, -1).join("\n").replace(/^ /gm, "") if testSSL - - var results = testSSL.scanResult[0] + - var results = testSSL.scanResult[0]; - h2(id="app:testssl") TestSSL Scan + h2(id="app:testssl") TestSSL scan - p The following data stems from a #[a(href="https://testssl.sh/") TestSSL] scan. The severity ratings are automatically computed by the TestSSL software without consideration of the specifics of the individual website. They do not reflect the opinions or views of the website evidence collector authors. + p The following data stems from a #[a(href="https://testssl.sh/") TestSSL] scan. The severity ratings are automatically computed by the TestSSL software without considering the security requirements of the individual website. They do not reflect the opinions or views of the website evidence collector's authors. - p.screen-only #[a(href="testssl/testssl.html") Click here] to check wether the full TestSSL scan report is available. + p.screen-only #[a(href="testssl/testssl.html") Click here] to check whether the full TestSSL scan report is available. table(width="100%") colgroup @@ -511,16 +558,16 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td OpenSSL version td= testSSL.openssl tr - td Target Host - td #{results.targetHost} (#{results.ip}) + td Target host + td #{ results.targetHost } (#{ results.ip }) - h3 Protocols + h3.unnumbered Protocols table(width="100%") colgroup - col(style='width: 0%') - col(style='width: 100%') - col(style='width: 0%') + col(style="width: 0%") + col(style="width: 100%") + col(style="width: 0%") thead tr th Protocol @@ -533,14 +580,14 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td= protocol.finding td.notrunc= protocol.severity - h3 HTTPS/SSL Vulnerabilities + h3.unnumbered HTTPS/SSL vulnerabilities table(width="100%") colgroup - col(style='width: 0%') - col(style='width: 80%') - col(style='width: 20%') - col(style='width: 0%') + col(style="width: 0%") + col(style="width: 80%") + col(style="width: 20%") + col(style="width: 0%") thead tr th Vulnerability @@ -552,21 +599,23 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') tr td.notrunc= vulnerability.id td.trunc(title=vulnerability.finding)= vulnerability.finding - td.trunc(title=vulnerability.cve).trunc + td.trunc.trunc(title=vulnerability.cve) if vulnerability.cve each cve in vulnerability.cve.split(' ') - a(href=`https://cve.mitre.org/cgi-bin/cvename.cgi?name=${cve}`)= cve + a( + href=`https://cve.mitre.org/cgi-bin/cvename.cgi?name=${cve}` + )= cve | td.notrunc= vulnerability.severity - h3 Cipher Categories + h3.unnumbered Cipher categories - table(style='width: 100%') + table(style="width: 100%") colgroup - col(style='width: 0%') - col(style='width: 100%') - col(style='width: 0%') - col(style='width: 0%') + col(style="width: 0%") + col(style="width: 100%") + col(style="width: 0%") + col(style="width: 0%") thead tr th Name @@ -580,16 +629,18 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td.notrunc= cipher.finding td.notrunc if cipher.cwe - a(href=`https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=${cipher.cwe.replace('CWE-','')}`)= cipher.cwe + a( + href=`https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=${cipher.cwe.replace('CWE-','')}` + )= cipher.cwe td.notrunc= cipher.severity - h3 HTTP Header Responses + h3.unnumbered HTTP header responses - table(style='width: 100%') + table(style="width: 100%") colgroup - col(style='width: 0%') - col(style='width: 100%') - col(style='width: 0%') + col(style="width: 0%") + col(style="width: 100%") + col(style="width: 0%") thead tr th Name @@ -602,29 +653,28 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') td.trunc(title=response.finding)= response.finding td.notrunc= response.severity - h2(id='app:glossary') Glossary + h2(id="app:glossary") Glossary dl - dt Filter Lists - dd Browser extensions commonly referred by #[em Adblocker] have been developed to block the loading of advertisements based on filter lists. Later on, filter lists have been extended to block also the loading of web page elements connected to the tracking of web page visitors. For this evidence collection, publicly available tracking filter lists are re-purposed to identify web page elements that may track the web page visitors. dt Do Not Track (DNT for short, HTTP) - dd The Do Not Track header is the proposed HTTP header field DNT that requests that a web service does not track its individual visitors. Note that this request cannot be enforced by technical means on the visitors’ side. It is upon the web service to take the DNT header field into account. For this evidence collection, the Do Not Track header is not employed. + dd The Do Not Track header is the proposed HTTP header field DNT, which requests that a web service does not track its individual visitors. Note that this request cannot be enforced by technical means on the visitors’ side. It is upon the web service to take the DNT header field into account. + dt Filter Lists + dd Browser extensions commonly referred to as #[em Adblockers] have been developed to block the loading of advertisements based on filter lists. Over time, these filter lists have been extended to also block the loading of web page elements associated with tracking web page visitors. For this evidence collection, publicly available tracking filter lists are used to identify web page elements that may track the web page visitors. dt First-Party - dd In this document, #[em first-party] is a classification of the resources links, web beacons, and cookies. To be first party, the resource domain must match the domain of the inspected web service or other configured first-party domains. Note that the resource path must also be within the path of the web service to be considered first-party. + dd In this document, #[em first-party] is a classification for resource links, web beacons, and cookies. To be considered first party, the resource’s domain must match the domain of the inspected web service or other configured first-party domains. Note that the resource path must also be within the path of the web service to be classified as first-party. dt Host (HTTP) - dd The HTTP #[em host] is the computer receiving and answering browser requests for web pages. + dd The HTTP #[em host] is the computer that receives and responds to browser requests for web pages. + dt Local Storage (HTML5) + dd Most web browsers allow web pages to store data locally in the browser profile. This #[em local storage] is specific to the website and persists through browser shutdowns. As embedded third-party resources may also have access to first-party local storage, it is classified both as first- and third-party. dt Redirect (HTTP) - dd A request for a web page may be answered with a new location (URL) to be requested instead. These HTTP #[em redirects] can be used to enforce the use of HTTPS. Visitors requested an HTTP web page are redirected to the corresponding HTTPS web page. + dd A request for a web page may be answered with a new location (URL) to be requested instead. These HTTP #[em redirects] can be used to enforce the use of HTTPS. When visitors request an HTTP web page, they are redirected to the corresponding HTTPS web page. dt Request (HTTP) - dd To download and display a web page identified by an URL, browsers send HTTP #[em requests] with the URL to the host computer specified as part of the URL. - dt Local Storage (HTML5) - dd Modern web browsers allow web pages to store data locally in the browser profile. This #[em local storage] is web site-specific and persistent through browser shutdowns. As embedded third-party resources may also have access to the first-party local storage, it is classified both as first- and third-party. + dd To download and display a web page identified by a URL, browsers send HTTP #[em requests] with the URL to the host computer specified as part of the URL. dt Third-Party dd Links, web beacons and cookies that are not #[em first-party] (see above) are classified as #[em third-party]. dt Web Beacon - dd A web beacon is one of various techniques used on web pages to unobtrusively (usually invisibly) allow tracking of web page visitors. A web beacon can be implemented for instance as a 1x1 pixel image, a transparent image, or an empty file that is requested together with other resources when a web page is loaded. + dd A web beacon is one of various techniques used on web pages to unobtrusively (usually invisibly) track web page visitors. A web beacon can be implemented as a 1x1 pixel image, a transparent image, or an empty file requested alongside other resources when a web page is loaded. dt Web Beacon Host - dd The #[em host] in the URL of a #[em request] of a #[em Web Beacon] is called #[em Web Beacon host]. - + dd The #[em host] in the URL of a #[em request] of a #[em web beacon] is referred to as the #[em web beacon host]. script. // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat @@ -634,29 +684,117 @@ html(xmlns='http://www.w3.org/1999/xhtml', xml:lang='en', lang='en') // Copyright (c) 2023 Bryan Braun; Licensed MIT // // @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat - !function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(globalThis,function(){"use strict";return function(A){function u(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"î§‹",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function d(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],u(this.options),this.add=function(A){var e,t,o,i,n,s,a,r,l,c,h,p=[];if(u(this.options),0!==(e=d(A=A||"h2, h3, h4, h5, h6")).length){for(null===document.head.querySelector("style.anchorjs")&&((A=document.createElement("style")).className="anchorjs",A.appendChild(document.createTextNode("")),void 0===(h=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(A):document.head.insertBefore(A,h), - A.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",A.sheet.cssRules.length),A.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)), - h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i<e.length;i++)if(this.hasAnchorJSLink(e[i]))p.push(i);else{if(e[i].hasAttribute("id"))o=e[i].getAttribute("id");else if(e[i].hasAttribute("data-anchor-id"))o=e[i].getAttribute("data-anchor-id");else{for(r=a=this.urlify(e[i].textContent),s=0;n=t.indexOf(r=void 0!==n?a+"-"+s:r),s+=1,-1!==n;);n=void 0,t.push(r),e[i].setAttribute("id",r),o=r}(l=document.createElement("a")).className="anchorjs-link "+this.options.class,l.setAttribute("aria-label",this.options.ariaLabel),l.setAttribute("data-anchorjs-icon",this.options.icon),this.options.titleText&&(l.title=this.options.titleText),c=document.querySelector("base")?window.location.pathname+window.location.search:"",c=this.options.base||c,l.href=c+"#"+o,"always"===this.options.visible&&(l.style.opacity="1"),"î§‹"===this.options.icon&&(l.style.font="1em/1 anchorjs-icons","left"===this.options.placement)&&(l.style.lineHeight="inherit"),"left"===this.options.placement?(l.style.position="absolute",l.style.marginLeft="-1.25em",l.style.paddingRight=".25em",l.style.paddingLeft=".25em",e[i].insertBefore(l,e[i].firstChild)):(l.style.marginLeft=".1875em",l.style.paddingRight=".1875em",l.style.paddingLeft=".1875em",e[i].appendChild(l))}for(i=0;i<p.length;i++)e.splice(p[i]-i,1);this.elements=this.elements.concat(e)}return this},this.remove=function(A){for(var e,t,o=d(A),i=0;i<o.length;i++)(t=o[i].querySelector(".anchorjs-link"))&&(-1!==(e=this.elements.indexOf(o[i]))&&this.elements.splice(e,1),o[i].removeChild(t));return this}, - this.removeAll=function(){this.remove(this.elements)},this.urlify=function(A){var e=document.createElement("textarea");return e.innerHTML=A,A=e.value,this.options.truncate||u(this.options),A.trim().replace(/'/gi,"").replace(/[& +$,:;=?@"#\u007B\u007D|^~[`%!'<>\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); + !(function (A, e) { + "use strict"; + "function" == typeof define && define.amd ? define([], e) : "object" == typeof module && module.exports ? (module.exports = e()) : ((A.AnchorJS = e()), (A.anchors = new A.AnchorJS())); + })(globalThis, function () { + "use strict"; + return function (A) { + function u(A) { + (A.icon = Object.prototype.hasOwnProperty.call(A, "icon") ? A.icon : "î§‹"), (A.visible = Object.prototype.hasOwnProperty.call(A, "visible") ? A.visible : "hover"), (A.placement = Object.prototype.hasOwnProperty.call(A, "placement") ? A.placement : "right"), (A.ariaLabel = Object.prototype.hasOwnProperty.call(A, "ariaLabel") ? A.ariaLabel : "Anchor"), (A.class = Object.prototype.hasOwnProperty.call(A, "class") ? A.class : ""), (A.base = Object.prototype.hasOwnProperty.call(A, "base") ? A.base : ""), (A.truncate = Object.prototype.hasOwnProperty.call(A, "truncate") ? Math.floor(A.truncate) : 64), (A.titleText = Object.prototype.hasOwnProperty.call(A, "titleText") ? A.titleText : ""); + } + function d(A) { + var e; + if ("string" == typeof A || A instanceof String) e = [].slice.call(document.querySelectorAll(A)); + else { + if (!(Array.isArray(A) || A instanceof NodeList)) throw new TypeError("The selector provided to AnchorJS was invalid."); + e = [].slice.call(A); + } + return e; + } + (this.options = A || {}), + (this.elements = []), + u(this.options), + (this.add = function (A) { + var e, + t, + o, + i, + n, + s, + a, + r, + l, + c, + h, + p = []; + if ((u(this.options), 0 !== (e = d((A = A || "h2, h3, h4, h5, h6"))).length)) { + for ( + null === document.head.querySelector("style.anchorjs") && (((A = document.createElement("style")).className = "anchorjs"), A.appendChild(document.createTextNode("")), void 0 === (h = document.head.querySelector('[rel="stylesheet"],style')) ? document.head.appendChild(A) : document.head.insertBefore(A, h), A.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}", A.sheet.cssRules.length), A.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}", A.sheet.cssRules.length), A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}", A.sheet.cssRules.length), A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}', A.sheet.cssRules.length)), + h = document.querySelectorAll("[id]"), + t = [].map.call(h, function (A) { + return A.id; + }), + i = 0; + i < e.length; + i++ + ) + if (this.hasAnchorJSLink(e[i])) p.push(i); + else { + if (e[i].hasAttribute("id")) o = e[i].getAttribute("id"); + else if (e[i].hasAttribute("data-anchor-id")) o = e[i].getAttribute("data-anchor-id"); + else { + for (r = a = this.urlify(e[i].textContent), s = 0; (n = t.indexOf((r = void 0 !== n ? a + "-" + s : r))), (s += 1), -1 !== n; ); + (n = void 0), t.push(r), e[i].setAttribute("id", r), (o = r); + } + ((l = document.createElement("a")).className = "anchorjs-link " + this.options.class), l.setAttribute("aria-label", this.options.ariaLabel), l.setAttribute("data-anchorjs-icon", this.options.icon), this.options.titleText && (l.title = this.options.titleText), (c = document.querySelector("base") ? window.location.pathname + window.location.search : ""), (c = this.options.base || c), (l.href = c + "#" + o), "always" === this.options.visible && (l.style.opacity = "1"), "î§‹" === this.options.icon && ((l.style.font = "1em/1 anchorjs-icons"), "left" === this.options.placement) && (l.style.lineHeight = "inherit"), "left" === this.options.placement ? ((l.style.position = "absolute"), (l.style.marginLeft = "-1.25em"), (l.style.paddingRight = ".25em"), (l.style.paddingLeft = ".25em"), e[i].insertBefore(l, e[i].firstChild)) : ((l.style.marginLeft = ".1875em"), (l.style.paddingRight = ".1875em"), (l.style.paddingLeft = ".1875em"), e[i].appendChild(l)); + } + for (i = 0; i < p.length; i++) e.splice(p[i] - i, 1); + this.elements = this.elements.concat(e); + } + return this; + }), + (this.remove = function (A) { + for (var e, t, o = d(A), i = 0; i < o.length; i++) (t = o[i].querySelector(".anchorjs-link")) && (-1 !== (e = this.elements.indexOf(o[i])) && this.elements.splice(e, 1), o[i].removeChild(t)); + return this; + }), + (this.removeAll = function () { + this.remove(this.elements); + }), + (this.urlify = function (A) { + var e = document.createElement("textarea"); + return ( + (e.innerHTML = A), + (A = e.value), + this.options.truncate || u(this.options), + A.trim() + .replace(/'/gi, "") + .replace(/[& +$,:;=?@"#\u007B\u007D|^~[`%!'<>\]./()*\\\n\t\b\v\u00A0]/g, "-") + .replace(/-{2,}/g, "-") + .substring(0, this.options.truncate) + .replace(/^-+|-+$/gm, "") + .toLowerCase() + ); + }), + (this.hasAnchorJSLink = function (A) { + var e = A.firstChild && -1 < (" " + A.firstChild.className + " ").indexOf(" anchorjs-link "), + A = A.lastChild && -1 < (" " + A.lastChild.className + " ").indexOf(" anchorjs-link "); + return e || A || !1; + }); + }; + }); // @license-end // Enable links for selected headers var anchors = new AnchorJS(); anchors.options = { - placement: 'right', + placement: "right", /* visible: 'always', */ /* icon: '¶', */ }; - anchors.add(":not(header) > h1, :not(header) > h2, h3, h4, h5, h6") + anchors.add(":not(header) > h1, :not(header) > h2, h3, h4, h5, h6"); script. function highlighter(item) { - item.addEventListener('dblclick', function() { - item.classList.toggle('highlighted'); - }, false); + item.addEventListener( + "dblclick", + function () { + item.classList.toggle("highlighted"); + }, + false, + ); } // highlighting for table cells - [].forEach.call(document.getElementsByTagName('td'), highlighter); + [].forEach.call(document.getElementsByTagName("td"), highlighter); // highlighting for list items - [].forEach.call(document.getElementsByTagName('li'), highlighter); - + [].forEach.call(document.getElementsByTagName("li"), highlighter); diff --git a/src/commands/reporterCommand.ts b/src/commands/reporterCommand.ts index d14690e2f935f28cf07d1ec76ae1dd3c3ebee476..3bf46d6ced46ccfa85d9635ec8d4630f596652f4 100644 --- a/src/commands/reporterCommand.ts +++ b/src/commands/reporterCommand.ts @@ -57,8 +57,8 @@ export default { .nargs("extra-file", 1) .alias("extra-file", "e") .array("extra-file") - .coerce("extra-file", (files) => { - return files.map((file) => { + .coerce("extra-file", (files: string[]) => { + return files.map((file: string) => { if ( file.toLowerCase().endsWith(".yaml") || file.toLowerCase().endsWith(".yml") @@ -83,24 +83,9 @@ export default { return true; }); }, - handler: async (argv: ParsedArgsReporter) => - await runReporter(transformArgsToObject(argv)), + handler: async (argv: any) => await runReporter(transformArgsToObject(argv)), }; -function transformArgsToObject(parsingResult): ParsedArgsReporter { - return { - _: parsingResult._ as string[], - inspectionJsonPath: parsingResult._[1] as string, - outputFile: parsingResult["output"] as string, - htmlTemplate: parsingResult["htmlTemplate"] as string | undefined, - officeTemplate: parsingResult["officeTemplate"] as string | undefined, - extraFile: parsingResult["extraFile"] as string | undefined, - usePandoc: parsingResult["usePandoc"] as boolean | undefined, - command: parsingResult["command"] as string, - }; -} - -/// Code that gets called when invoking the reporter command using the CLI async function runReporter(args: ParsedArgsReporter) { let output = JSON.parse(fs.readFileSync(args.inspectionJsonPath, "utf8")); @@ -110,11 +95,6 @@ async function runReporter(args: ParsedArgsReporter) { args.officeTemplate || path.join(__dirname, "../assets/template-office.pug"); - let jsondir = path.relative( - args.outputFile ? path.dirname(args.outputFile) : process.cwd(), - path.dirname(args.inspectionJsonPath), - ); // path of the inspection.json - // it is surprising that https://github.com/jstransformers/jstransformer-marked picks up this object (undocumented API) // source of this call: https://github.com/markedjs/marked-custom-heading-id/blob/main/src/index.js (MIT License, Copyright (c) 2021 @markedjs) marked.use({ @@ -143,7 +123,6 @@ async function runReporter(args: ParsedArgsReporter) { Object.assign({}, output, { pretty: true, basedir: path.resolve(path.join(__dirname, "../assets")), // determines root director for pug - jsondir: jsondir || ".", // expose some libraries to pug templates groupBy: groupBy, marked: marked, // we need to pass the markdown engine to template for access at render-time (as opposed to comile time), see https://github.com/pugjs/pug/issues/1171 @@ -155,7 +134,7 @@ async function runReporter(args: ParsedArgsReporter) { "utf8", ), inspection: output, - extra: args.extraFile, + extra: args.extraFiles, filterOptions: { marked: {} }, }), ); @@ -258,14 +237,23 @@ async function generatePdf(outputFile: string, html_dump: string) { }); await browser.close(); } - +function transformArgsToObject(parsingResult: any): ParsedArgsReporter { + return { + _: parsingResult._ as string[], + inspectionJsonPath: parsingResult._[1] as string, + outputFile: parsingResult["outputFile"] as string, + htmlTemplate: parsingResult["htmlTemplate"] as string | undefined, + officeTemplate: parsingResult["officeTemplate"] as string | undefined, + extraFiles: parsingResult["extraFile"] as string[] | undefined, + usePandoc: parsingResult["usePandoc"] as boolean | undefined, + }; +} interface ParsedArgsReporter { _: (string | number)[]; - command: string; inspectionJsonPath: string; outputFile?: string; htmlTemplate?: string; officeTemplate?: string; - extraFile?: string; + extraFiles?: string[]; usePandoc?: boolean; } diff --git a/src/reporter/reporter.ts b/src/reporter/reporter.ts index 74c9e985138fb7c51b36255f78b7b0ae8a382610..774f7ccd0927436de0d2a64edd9bba8cd6fe303b 100644 --- a/src/reporter/reporter.ts +++ b/src/reporter/reporter.ts @@ -97,7 +97,6 @@ export class Reporter { Object.assign({}, data, { pretty: true, basedir: path.join(__dirname, "../assets"), - jsondir: ".", // images in the folder of the inspection.json groupBy: groupBy, marked: marked, // we need to pass the markdown engine to template for access at render-time (as opposed to comile time), see https://github.com/pugjs/pug/issues/1171 fs: fs, @@ -126,9 +125,15 @@ export class Reporter { pdffilename = "inspection.pdf", ) { if (this.args.pdf && this.args.outputPath) { - let content = fs.readFileSync(path.resolve(path.join(this.args.outputPath, htmlfilename)), "utf8"); + let content = fs.readFileSync( + path.resolve(path.join(this.args.outputPath, htmlfilename)), + "utf8", + ); let pdfBuffer = await this.convertHtmlToPdfInMemory(content); - fs.writeFileSync(path.resolve(path.join(this.args.outputPath, pdffilename)), pdfBuffer); + fs.writeFileSync( + path.resolve(path.join(this.args.outputPath, pdffilename)), + pdfBuffer, + ); } }