domain-security-check 1.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. domain_security_check-1.7.0/LICENSE +21 -0
  2. domain_security_check-1.7.0/PKG-INFO +339 -0
  3. domain_security_check-1.7.0/README.md +300 -0
  4. domain_security_check-1.7.0/domain_security_check.egg-info/PKG-INFO +339 -0
  5. domain_security_check-1.7.0/domain_security_check.egg-info/SOURCES.txt +71 -0
  6. domain_security_check-1.7.0/domain_security_check.egg-info/dependency_links.txt +1 -0
  7. domain_security_check-1.7.0/domain_security_check.egg-info/entry_points.txt +2 -0
  8. domain_security_check-1.7.0/domain_security_check.egg-info/requires.txt +18 -0
  9. domain_security_check-1.7.0/domain_security_check.egg-info/top_level.txt +1 -0
  10. domain_security_check-1.7.0/domaincheck/__init__.py +6 -0
  11. domain_security_check-1.7.0/domaincheck/__main__.py +7 -0
  12. domain_security_check-1.7.0/domaincheck/_version.py +1 -0
  13. domain_security_check-1.7.0/domaincheck/audit.py +256 -0
  14. domain_security_check-1.7.0/domaincheck/branding.py +82 -0
  15. domain_security_check-1.7.0/domaincheck/cli.py +257 -0
  16. domain_security_check-1.7.0/domaincheck/collector.py +675 -0
  17. domain_security_check-1.7.0/domaincheck/ct.py +81 -0
  18. domain_security_check-1.7.0/domaincheck/demo.py +125 -0
  19. domain_security_check-1.7.0/domaincheck/dns_checks.py +317 -0
  20. domain_security_check-1.7.0/domaincheck/email_checks.py +469 -0
  21. domain_security_check-1.7.0/domaincheck/history.py +106 -0
  22. domain_security_check-1.7.0/domaincheck/hosting.py +199 -0
  23. domain_security_check-1.7.0/domaincheck/hsts_preload.py +64 -0
  24. domain_security_check-1.7.0/domaincheck/i18n.py +115 -0
  25. domain_security_check-1.7.0/domaincheck/locales/__init__.py +1 -0
  26. domain_security_check-1.7.0/domaincheck/locales/fr.py +1024 -0
  27. domain_security_check-1.7.0/domaincheck/models.py +240 -0
  28. domain_security_check-1.7.0/domaincheck/nis2.py +72 -0
  29. domain_security_check-1.7.0/domaincheck/observatory.py +165 -0
  30. domain_security_check-1.7.0/domaincheck/pdf_report.py +374 -0
  31. domain_security_check-1.7.0/domaincheck/py.typed +0 -0
  32. domain_security_check-1.7.0/domaincheck/rdap.py +110 -0
  33. domain_security_check-1.7.0/domaincheck/report.py +564 -0
  34. domain_security_check-1.7.0/domaincheck/scoring.py +72 -0
  35. domain_security_check-1.7.0/domaincheck/smtp_scan.py +153 -0
  36. domain_security_check-1.7.0/domaincheck/software.py +225 -0
  37. domain_security_check-1.7.0/domaincheck/sources.py +125 -0
  38. domain_security_check-1.7.0/domaincheck/summary.py +137 -0
  39. domain_security_check-1.7.0/domaincheck/timeline.py +68 -0
  40. domain_security_check-1.7.0/domaincheck/tls_checks.py +159 -0
  41. domain_security_check-1.7.0/domaincheck/tls_scan.py +175 -0
  42. domain_security_check-1.7.0/domaincheck/trackers.py +196 -0
  43. domain_security_check-1.7.0/domaincheck/web_checks.py +563 -0
  44. domain_security_check-1.7.0/pyproject.toml +80 -0
  45. domain_security_check-1.7.0/setup.cfg +4 -0
  46. domain_security_check-1.7.0/tests/test_audit.py +240 -0
  47. domain_security_check-1.7.0/tests/test_branding.py +79 -0
  48. domain_security_check-1.7.0/tests/test_cli.py +71 -0
  49. domain_security_check-1.7.0/tests/test_collector.py +66 -0
  50. domain_security_check-1.7.0/tests/test_ct.py +177 -0
  51. domain_security_check-1.7.0/tests/test_dkim.py +87 -0
  52. domain_security_check-1.7.0/tests/test_dns.py +184 -0
  53. domain_security_check-1.7.0/tests/test_email.py +186 -0
  54. domain_security_check-1.7.0/tests/test_history.py +139 -0
  55. domain_security_check-1.7.0/tests/test_hosting.py +97 -0
  56. domain_security_check-1.7.0/tests/test_hsts_preload.py +61 -0
  57. domain_security_check-1.7.0/tests/test_http_identity.py +170 -0
  58. domain_security_check-1.7.0/tests/test_i18n.py +147 -0
  59. domain_security_check-1.7.0/tests/test_nis2.py +50 -0
  60. domain_security_check-1.7.0/tests/test_observatory.py +132 -0
  61. domain_security_check-1.7.0/tests/test_pdf.py +46 -0
  62. domain_security_check-1.7.0/tests/test_rdap.py +132 -0
  63. domain_security_check-1.7.0/tests/test_scoring.py +63 -0
  64. domain_security_check-1.7.0/tests/test_security_txt.py +74 -0
  65. domain_security_check-1.7.0/tests/test_smtp.py +189 -0
  66. domain_security_check-1.7.0/tests/test_software.py +116 -0
  67. domain_security_check-1.7.0/tests/test_sources.py +83 -0
  68. domain_security_check-1.7.0/tests/test_subdomain_audits.py +71 -0
  69. domain_security_check-1.7.0/tests/test_summary.py +98 -0
  70. domain_security_check-1.7.0/tests/test_timeline.py +86 -0
  71. domain_security_check-1.7.0/tests/test_tls_scan.py +209 -0
  72. domain_security_check-1.7.0/tests/test_trackers.py +114 -0
  73. domain_security_check-1.7.0/tests/test_web.py +257 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Loïc Tellier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,339 @@
1
+ Metadata-Version: 2.4
2
+ Name: domain-security-check
3
+ Version: 1.7.0
4
+ Summary: Passive security audit of a domain name (HTTPS, security headers, SPF/DKIM/DMARC), with a clear HTML report.
5
+ Author: Loïc Tellier
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Ryarkhewas/domain-security-check
8
+ Project-URL: Changelog, https://github.com/Ryarkhewas/domain-security-check/blob/main/CHANGELOG.md
9
+ Keywords: security,audit,dmarc,spf,dkim,hsts,csp,http-headers
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Information Technology
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Security
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: cryptography>=41
24
+ Requires-Dist: dnspython>=2.4
25
+ Requires-Dist: requests>=2.31
26
+ Requires-Dist: tldextract>=5.1
27
+ Provides-Extra: pdf
28
+ Requires-Dist: reportlab>=4.0; extra == "pdf"
29
+ Provides-Extra: browser
30
+ Requires-Dist: playwright>=1.40; extra == "browser"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=8.0; extra == "dev"
33
+ Requires-Dist: ruff>=0.6; extra == "dev"
34
+ Requires-Dist: mypy>=1.10; extra == "dev"
35
+ Requires-Dist: types-requests; extra == "dev"
36
+ Requires-Dist: build; extra == "dev"
37
+ Requires-Dist: reportlab>=4.0; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # domain-security-check
41
+
42
+ [![Tests](https://github.com/Ryarkhewas/domain-security-check/actions/workflows/tests.yml/badge.svg)](https://github.com/Ryarkhewas/domain-security-check/actions/workflows/tests.yml) [![Live check](https://github.com/Ryarkhewas/domain-security-check/actions/workflows/live-check.yml/badge.svg)](https://github.com/Ryarkhewas/domain-security-check/actions/workflows/live-check.yml)
43
+
44
+ **Passive security audit of a domain name, with a clear report for non-specialists.**
45
+
46
+ In a single command, the tool checks what anyone can see from the Internet about a domain: is the website properly protected with HTTPS, does the server send the right security headers, and above all, **can someone send emails pretending to be the company?** It produces an HTML report (and, if you want, a PDF) with a grade per area (website, certificate, email, DNS), the points behind each grade, the standard each check relies on, and concrete actions to take. Several domains can be audited at once, with a summary page. Reports exist in English and French, and each new audit of a domain shows what changed since the previous one.
47
+
48
+ ![Report preview](https://raw.githubusercontent.com/Ryarkhewas/domain-security-check/main/examples/preview.png)
49
+
50
+ Example reports: [HTML](https://github.com/Ryarkhewas/domain-security-check/blob/main/examples/demo_report.html) and [PDF](https://github.com/Ryarkhewas/domain-security-check/blob/main/examples/demo_report.pdf), the same in French: [PDF](https://github.com/Ryarkhewas/domain-security-check/blob/main/examples/demo_report_fr.pdf), and a branded French PDF with a cover page, the subdomain checks and the NIS 2 view: [PDF](https://github.com/Ryarkhewas/domain-security-check/blob/main/examples/demo_report_branded_fr.pdf).
51
+
52
+ ## What is checked
53
+
54
+ | Area | Checks |
55
+ |---|---|
56
+ | **Website** | HTTP to HTTPS redirect, HSTS (including the browsers' preload list), Content-Security-Policy, cookies (Secure, HttpOnly, SameSite), Subresource Integrity of external scripts, clickjacking protection, X-Content-Type-Options, Referrer-Policy, Cross-Origin-Resource/Opener/Embedder-Policy; not scored: Permissions-Policy, software version disclosure, security.txt (how to report a vulnerability), outdated software (WordPress, Joomla, Drupal, TYPO3, PrestaShop and PHP recognised on the home page, with their support status), advertising and analytics trackers loaded before consent (Google Analytics, Meta Pixel, Hotjar…) |
57
+ | **Certificate & TLS** | Valid certificate, expiry date, key strength; protocol versions accepted (SSL 3.0 to TLS 1.3), broken cipher suites (RC4, 3DES, export, no encryption), forward secrecy |
58
+ | **Email** | MX servers, SPF (presence, `all` rule, 10 DNS lookup limit), DMARC (policy, coverage, reports, inheritance from the parent domain for subdomains), DKIM (about 50 common selectors, including those of the sending services named in SPF; key strength: RSA under 2048 bits flagged), MTA-STS (record, policy file, mode, coverage of the mail servers), the mail servers themselves (STARTTLS offered, TLS version, certificate, as a sending server sees them); not scored: TLS reporting (TLS-RPT), DANE records |
59
+ | **Domain & DNS** | DNSSEC (enabled, and consistent between the parent zone and the domain's keys), CAA (which certificate authorities may issue certificates, and whether the authority of the certificate in use is one of them), expiry date of the domain and transfer lock (from the registry, through RDAP), forgotten subdomains: names published in Certificate Transparency logs whose DNS record points to a resource that no longer exists (subdomain takeover) |
60
+
61
+ Each item is rated **Critical**, **To improve**, **Compliant** or **Info**, shows the points it weighs in the grade, cites the standard it is based on (and the matching ANSSI or CNIL recommendation, when there is one), and comes with a plain-language recommendation.
62
+
63
+ **Optional, for a deeper audit:**
64
+ - `--subdomains` also checks every subdomain in use found in the Certificate Transparency logs: HTTPS available, valid certificate, HTTP to HTTPS redirect, TLS 1.0/1.1 still accepted, outdated software. It takes a few requests per subdomain, so it is off by default. These items are not scored (the grades describe the domain itself) but appear among the priority actions.
65
+ - `--browser` loads the home page in a real browser (headless Chromium), without clicking anything, to see the trackers that are contacted or set cookies before consent, including those a tag manager adds in JavaScript. Without it, trackers are looked for in the page received only. It needs Playwright: `pip install "domain-security-check[browser]"`, then `playwright install chromium`.
66
+ - `--nis2` adds a **NIS 2 view**: for each of the ten measures of article 21(2) of [Directive (EU) 2022/2555](https://eur-lex.europa.eu/eli/dir/2022/2555/oj) (encryption, maintenance and vulnerability handling, cyber hygiene, asset management…), the related checks of the audit and the problems found. Measures that cannot be seen from the Internet (risk analysis, incident handling, supply chain, training…) are shown as such: the table links checks to measures, it is not a compliance assessment.
67
+
68
+ **Summary for decision makers.** The report opens with the main risks in business terms ("anyone can send emails in the company's name"), the effort needed (quick fixes under 30 minutes, a few hours, or a project needing a developer or a provider) and the overall grade the quick fixes alone would give. Each priority action shows its impact and its effort, and the list starts with what pays off most for the least work.
69
+
70
+ **Fixes for the platform in use.** The tool recognises the web server or hosting platform (Apache, nginx, IIS, LiteSpeed, Cloudflare, Netlify, Vercel, GitHub Pages) from the HTTP headers, the DNS host from the NS records (OVHcloud, Cloudflare, Gandi, IONOS, Route 53…) and the email provider from the MX records (Google Workspace, Microsoft 365…). Each problem then comes with the configuration line for that server, or the menu to open at that provider, with a link to its documentation.
71
+
72
+ ## A strictly passive audit
73
+
74
+ The tool only does what any visitor or mail server does: public DNS lookups, HTTPS connections, and requests to the home page (over HTTPS and HTTP) and to `robots.txt`, like Mozilla HTTP Observatory. **No port scanning, no intrusion testing.** It also reads the site's public `security.txt` file and, for email, the MTA-STS policy file when the domain announces one, as any sending mail server does. To check the mail servers, it connects to them on port 25 like a sending server, asks for encryption (STARTTLS), completes the TLS handshake and says goodbye (QUIT): no email is ever sent. Port 25 is blocked by many networks (home connections, cloud providers); the report then says nothing could be concluded, and `--no-mail-servers` skips this step. The registration data of the domain (expiry date, status) comes from the registry's public RDAP service, the successor of WHOIS. The support dates of software versions come from [endoflife.date](https://endoflife.date/) (cached for three days). The other downloads are public lists, each cached for a week: the [HSTS preload list](https://hstspreload.org/) and the [IANA list of RDAP servers](https://data.iana.org/rdap/dns.json). To see which TLS versions and cipher suites the server accepts, it sends a few times the first message of a TLS connection (the ClientHello an old or a current browser would send), reads the server's answer and closes: no key exchange, no data. Subdomains are read from public Certificate Transparency logs ([crt.sh](https://crt.sh/), or [Cert Spotter](https://sslmate.com/certspotter/) when crt.sh does not answer); each one is only looked up in DNS, never contacted. If the local DNS resolver refuses DNSSEC queries (some routers and Linux setups do), those queries are retried through public resolvers (Cloudflare, Quad9, Google).
75
+
76
+ Requests identify themselves as a current desktop browser, with `domain-security-check` appended to the User-Agent. Some sites answer differently to bots (no HTTPS redirect, missing headers): this way the report shows what real visitors get, while the tool stays identifiable in server logs.
77
+
78
+ Even passive, an audit should only be run **on your own domains or with the owner's permission**.
79
+
80
+ ## Installation
81
+
82
+ Requires Python 3.10 or later.
83
+
84
+ ```bash
85
+ # From PyPI (once published there, see "Publishing" below)
86
+ pip install domain-security-check # add [pdf], [browser] or [pdf,browser] for the options
87
+
88
+ # Latest release, straight from GitHub
89
+ pip install "git+https://github.com/Ryarkhewas/domain-security-check@v1.7.0"
90
+
91
+ # With PDF output (add ,browser for --browser)
92
+ pip install "domain-security-check[pdf] @ git+https://github.com/Ryarkhewas/domain-security-check@v1.7.0"
93
+
94
+ # Or from a clone, to work on the code
95
+ git clone https://github.com/Ryarkhewas/domain-security-check.git
96
+ cd domain-security-check
97
+ python -m venv .venv
98
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
99
+ pip install -e ".[dev]"
100
+ ```
101
+
102
+ The wheel and source archive of each version are also attached to the [GitHub releases](https://github.com/Ryarkhewas/domain-security-check/releases).
103
+
104
+ **With Docker**, nothing to install but Docker. Reports (and the history of each domain) are written to the folder mounted on `/reports`:
105
+
106
+ ```bash
107
+ docker run --rm -v "$PWD/reports:/reports" ghcr.io/ryarkhewas/domain-security-check example.com --pdf --lang fr
108
+ # With a real browser for --browser (larger image)
109
+ docker run --rm -v "$PWD/reports:/reports" ghcr.io/ryarkhewas/domain-security-check:browser example.com --browser
110
+ ```
111
+
112
+ The images are published with each release (`latest` and the version number, plus `browser` and `<version>-browser`), and can be built from the repository with `docker build -t domain-security-check .` (add `--target browser` for the larger one).
113
+
114
+ ## Usage
115
+
116
+ ```bash
117
+ # Audit a domain (report in reports/<domain>.html)
118
+ domain-security-check my-domain.com
119
+
120
+ # Choose the output file and sign the report
121
+ domain-security-check my-domain.com -o reports/client.html --author "Your Name"
122
+
123
+ # Also produce a PDF (install the extra first: pip install "domain-security-check[pdf]")
124
+ domain-security-check my-domain.com --pdf
125
+
126
+ # Report in French
127
+ domain-security-check my-domain.com --lang fr
128
+
129
+ # Several domains: one report each, plus reports/index.html to compare them
130
+ domain-security-check client-a.com client-b.fr client-c.org
131
+ domain-security-check --file domains.txt --output-dir reports/clients
132
+
133
+ # Branded PDF for a client: cover page with your logo, company and contact
134
+ domain-security-check client.fr --pdf --lang fr --brand examples/branding.example.json --client "Client name"
135
+ domain-security-check client.fr --pdf --company "Your company" --logo logo.png --color "#0f766e"
136
+
137
+ # Skip the Certificate Transparency logs (faster: crt.sh can take a minute on large domains)
138
+ domain-security-check my-domain.com --no-ct-logs
139
+
140
+ # Deeper audit: subdomains in use, trackers in a real browser, and the NIS 2 view
141
+ domain-security-check client.fr --subdomains --browser --nis2 --pdf --lang fr
142
+
143
+ # Skip the connections to the mail servers on port 25
144
+ domain-security-check my-domain.com --no-mail-servers
145
+
146
+ # JSON output (to feed another tool)
147
+ domain-security-check my-domain.com --json
148
+
149
+ # Demo on a fictitious domain, without network access
150
+ domain-security-check --demo
151
+ ```
152
+
153
+ A domain file has one domain per line; blank lines and `#` comments are ignored. Several domains are audited at the same time (6 by default, `--workers` to change it). With several domains, a domain that cannot be audited (DNS failure, invalid name) is listed on the summary page with the reason, the others are still audited, and the command ends with exit code 3.
154
+
155
+ `python -m domaincheck` works the same way, and so does `python domain_check.py` from a clone, without installing.
156
+
157
+ A full URL is accepted (`https://www.my-domain.com/contact`): the tool extracts the domain. Email checks run on the main domain, even if you enter `www.`.
158
+
159
+ ## Branded reports
160
+
161
+ With `--company`, `--contact`, `--client`, `--logo` (PNG or JPEG) or `--color`, the PDF starts with a cover page: your logo, the domain, "Prepared for" the client, the date, the overall grade, your name, company and contact, and a confidentiality note. The accent colour is used for the headings, and your company appears in the footer of every page. The HTML report ends with the same details. To avoid typing them each time, put them in a JSON file and pass it with `--brand` ([example](https://github.com/Ryarkhewas/domain-security-check/blob/main/examples/branding.example.json)); command-line values take precedence over the file.
162
+
163
+ ## Changes since the previous audit
164
+
165
+ Each report is saved with its result in JSON (`<domain>.json`, next to the HTML report). When the same domain is audited again into the same folder, the new report starts with what changed since then: grades that went up or down, problems fixed, new problems, and problems that got worse or better. The summary page of several domains shows it in one column. This is meant for a regular follow-up, for instance a weekly audit of a client's domains.
166
+
167
+ `--previous FILE` compares a single domain with any earlier JSON result.
168
+
169
+ **Grades over time.** Every audit also adds a line to `<domain>.history.json` (date, overall grade, grade and score of each area). From the second audit on, the HTML and PDF reports show a chart of the four scores over time, with the values in a table.
170
+
171
+ ## Languages
172
+
173
+ `--lang fr` produces the HTML report, the PDF, the summary page and the console summary in French. All texts come from one catalog (`domaincheck/locales/fr.py`), and a test checks that every text of the code has its translation. The JSON output always stays in English, as it is meant for other programs.
174
+
175
+ ## Reliable diagnosis
176
+
177
+ If a DNS query fails (network outage, timeout), the audit **stops without producing a report** rather than wrongly concluding "no SPF" or "no DMARC". A missing record and a check that could not be performed are two different things.
178
+
179
+ For DKIM, there is no way to list all of a domain's keys: the tool tests about 50 common selectors, including the usual ones of the sending services named in the SPF record (Google Workspace, Microsoft 365, SendGrid, Mailchimp…). Finding nothing is reported as information, not as a flaw; when a service named in SPF has none of its usual keys, the report says DKIM is probably not enabled there.
180
+
181
+ ## Grades
182
+
183
+ The report gives **four grades**, one per area, and an **overall grade equal to the weakest of the four**: a site is only as safe as its weakest point.
184
+
185
+ | Area | How it is graded |
186
+ |---|---|
187
+ | **Website** | The [Mozilla HTTP Observatory](https://developer.mozilla.org/en-US/observatory/docs/tests_and_scoring) algorithm, with its official score modifiers: start at 100, apply the penalties, and add bonuses only if the score is still 90 or more. |
188
+ | **Certificate & TLS** | This project's scale, based on RFC 8996 and RFC 9325: −100 for a missing, invalid or expired certificate, −30 for a connection negotiated in TLS 1.0/1.1, SSL 3.0 accepted, a key that is too short (RSA under 2048 bits) or a certificate expiring within 15 days, −20 for TLS 1.0/1.1 still accepted or broken cipher suites accepted, −10 for no forward secrecy with current browsers or a certificate expiring within 30 days, −5 for no TLS 1.3 or the RSA key exchange still accepted for old clients. |
189
+ | **Email** | This project's scale, based on what the standards define as valid: e.g. no DMARC −40, no SPF −30 (−10 if a strict DMARC policy already stops spoofing), `p=none` −20, `?all` −20, no MTA-STS −5, MTA-STS in enforce mode that leaves out a mail server −30, a mail server without STARTTLS −20, an obsolete TLS version on a mail server −10, invalid mail server certificates −5 (−30 if MTA-STS is in enforce mode: senders that apply it refuse to deliver). |
190
+ | **Domain & DNS** | This project's scale, kept moderate since many large sites still do without these: DNSSEC not enabled −15, broken DNSSEC −50 (resolvers may refuse the domain), no CAA record −5, domain expiring within 30 days −10 (within 15 days −30, expired −50), no transfer lock −5, a subdomain open to takeover −30 (another dangling DNS record −10). |
191
+
192
+ All four use the Observatory grade chart (A+ … F, by steps of 5 points); A+ only exists for the website grade, the only one with bonuses.
193
+
194
+ Each item of the report shows the points it adds or removes, so every grade can be recomputed by hand.
195
+
196
+ **Broken HTTPS caps the website grade at F.** If the certificate is invalid or expired, or if the site answers over HTTP but not over HTTPS, visitors are not protected whatever the other settings, and the security headers cannot even be checked. A domain with no website at all (mail only) gets "N/A" for the website and TLS grades, and is graded on email and DNS only.
197
+
198
+ ## Methodology
199
+
200
+ Every check is based on a published standard or guide, cited under each item of the report.
201
+
202
+ | Check | Reference |
203
+ |---|---|
204
+ | Content-Security-Policy | [W3C CSP Level 3](https://www.w3.org/TR/CSP3/) |
205
+ | HSTS | [RFC 6797](https://www.rfc-editor.org/rfc/rfc6797), [HSTS preload list](https://hstspreload.org/) (Chromium's list, used by all major browsers) |
206
+ | Subresource Integrity | [W3C SRI](https://www.w3.org/TR/SRI/) |
207
+ | Clickjacking (X-Frame-Options, frame-ancestors) | [RFC 7034](https://www.rfc-editor.org/rfc/rfc7034) |
208
+ | X-Content-Type-Options | [WHATWG Fetch](https://fetch.spec.whatwg.org/#x-content-type-options-header) |
209
+ | Referrer-Policy | [W3C Referrer Policy](https://www.w3.org/TR/referrer-policy/) |
210
+ | Cookies | [RFC 6265](https://www.rfc-editor.org/rfc/rfc6265), [MDN cookie guide](https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Cookies) |
211
+ | HTTP to HTTPS redirect | [MDN TLS guide](https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/TLS#http_redirection) |
212
+ | Cross-Origin-Resource / Opener / Embedder-Policy | MDN reference pages |
213
+ | Permissions-Policy, version disclosure (not scored) | [W3C Permissions Policy](https://www.w3.org/TR/permissions-policy/), [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/) |
214
+ | TLS protocol version | [RFC 8996](https://www.rfc-editor.org/rfc/rfc8996) (TLS 1.0 and 1.1 deprecated), [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446) (TLS 1.3) |
215
+ | Cipher suites, forward secrecy, key size | [RFC 9325](https://www.rfc-editor.org/rfc/rfc9325) (recommendations for the secure use of TLS) |
216
+ | SPF | [RFC 7208](https://www.rfc-editor.org/rfc/rfc7208) |
217
+ | DMARC, including subdomain inheritance | [RFC 7489](https://www.rfc-editor.org/rfc/rfc7489) |
218
+ | DKIM | [RFC 6376](https://www.rfc-editor.org/rfc/rfc6376), [RFC 8301](https://www.rfc-editor.org/rfc/rfc8301) (RSA keys of at least 1024 bits, 2048 recommended) |
219
+ | Domains that receive no email | [RFC 7505](https://www.rfc-editor.org/rfc/rfc7505) (null MX) |
220
+ | MTA-STS, TLS reporting | [RFC 8461](https://www.rfc-editor.org/rfc/rfc8461), [RFC 8460](https://www.rfc-editor.org/rfc/rfc8460) |
221
+ | Mail servers: STARTTLS, DANE | [RFC 3207](https://www.rfc-editor.org/rfc/rfc3207), [RFC 7672](https://www.rfc-editor.org/rfc/rfc7672); ANSSI PA-066 R42 (STARTTLS on SMTP servers) |
222
+ | DNSSEC | [RFC 4033](https://www.rfc-editor.org/rfc/rfc4033) to 4035 |
223
+ | CAA | [RFC 8659](https://www.rfc-editor.org/rfc/rfc8659); CAA identifiers published by each authority |
224
+ | Trackers before consent (not scored) | [CNIL rules on cookies and other trackers](https://www.cnil.fr/fr/cookies-et-autres-traceurs/regles/cookies) (ePrivacy directive, article 5(3)) |
225
+ | Domain registration data (expiry, status) | [RFC 9083](https://www.rfc-editor.org/rfc/rfc9083) (RDAP), [RFC 9224](https://www.rfc-editor.org/rfc/rfc9224) (finding the RDAP server), [RFC 5731](https://www.rfc-editor.org/rfc/rfc5731#section-2.3) (transfer lock status) |
226
+ | Forgotten subdomains | [RFC 9162](https://www.rfc-editor.org/rfc/rfc9162) (Certificate Transparency); ANSSI PA-009 R3 recommends watching CT logs |
227
+ | Outdated software (not scored) | Support dates from [endoflife.date](https://endoflife.date/); the CNIL website sheet asks to keep components updated |
228
+ | security.txt (not scored) | [RFC 9116](https://www.rfc-editor.org/rfc/rfc9116) |
229
+ | Website scoring | [Mozilla HTTP Observatory](https://github.com/mdn/mdn-http-observatory) (MPL 2.0) |
230
+
231
+ **ANSSI guides.** For a French audience, each item also cites, when there is one, the matching recommendation of the guides published by [ANSSI](https://cyber.gouv.fr/) (the French national cybersecurity agency). Only recommendation numbers read in the guides themselves are cited; when a topic has no numbered recommendation, the section is cited instead.
232
+
233
+ | Guide | Cited for |
234
+ |---|---|
235
+ | [Recommandations pour la mise en œuvre d'un site web](https://cyber.gouv.fr/uploads/2013/05/anssi-guide-recommandations_mise_en_oeuvre_site_web_maitriser_standards_securite_cote_navigateur-v2.0.pdf) (ANSSI-PA-009, v2.0, 2021) | HTTPS redirect and TLS (R1), HSTS (R2), X-Content-Type-Options (R6), Subresource Integrity (R11, R12), CSP (R14 to R16), clickjacking (R17, R18), Referrer-Policy (§5.4), cookies (§5.5.3) |
236
+ | [Recommandations de sécurité relatives à TLS](https://messervices.cyber.gouv.fr/documents-guides/anssi-guide-recommandations_de_securite_relatives_a_tls-v1.2.pdf) (v1.2, 2020) | TLS version (R3, R4), certificate (R8) |
237
+ | [Recommandations relatives à l'interconnexion d'un SI à Internet](https://messervices.cyber.gouv.fr/documents-guides/anssi-guide-passerelle_internet_securisee-v3.pdf) (ANSSI-PA-066, v3.0, 2020) | STARTTLS (R42), SPF (R44), DKIM (R46+), DMARC (R48+), DNSSEC of email domains (R50+), MTA-STS, TLS reporting and DANE (§5.3) |
238
+ | [Bonnes pratiques pour l'acquisition et l'exploitation de noms de domaine](https://messervices.cyber.gouv.fr/documents-guides/guide_dns_fr_anssi_1.3.pdf) (ANSSI-BP-038, v1.3, 2017) | transfer lock (R1, R3+), DNSSEC (R4+), domain expiry (§4.10) |
239
+
240
+ The [CNIL](https://www.cnil.fr/) (the French data protection authority) is also cited for the few topics its sheet [Sécuriser les sites web](https://www.cnil.fr/fr/securite-securiser-les-sites-web) (personal data security guide, 2024 edition) covers: certificate, TLS version, HTTPS redirect and cookies. Its guide is general and refers to ANSSI for technical details, but for a French company it links the website's security to the GDPR's security obligation.
241
+
242
+ A citation says which recommendation a check relates to. It does not mean the domain complies with ANSSI guidance: most recommendations also cover settings that cannot be seen from outside. This project is not affiliated with ANSSI or CNIL.
243
+
244
+ **Differences with the Observatory.** One Observatory test is not reproduced: CORS, whose only penalty requires sending a forged `Origin` header. A Content-Security-Policy declared in a `<meta http-equiv>` tag is not read (the `<meta name="referrer">` tag is), and only the first CSP policy is analysed when a site sends several. In these rare cases the website grade can differ from the Observatory's.
245
+
246
+ **Checked against the Observatory every week.** The Live check workflow asks the [Observatory API](https://github.com/mdn/mdn-http-observatory) for its result on the same domains and compares it test by test. Any test where the points differ is listed, and a grade gap raises a warning. This is how the HSTS preload, Subresource Integrity, cookie collection and `<meta name="referrer">` rules were found and fixed; on 14 October 2026, all six domains (github.com, google.com, cloudflare.com, wikipedia.org, docs.github.com, badssl.com) got exactly the same score, with the same points on every test.
247
+
248
+ ## Project structure
249
+
250
+ ```
251
+ domain_check.py Runs the tool from a clone, without installing
252
+ pyproject.toml Package definition, dependencies, tool settings
253
+ Dockerfile Docker images (standard and with a browser)
254
+ domaincheck/
255
+ cli.py Command line (domain-security-check)
256
+ collector.py Network access (DNS, TLS, HTTP, CT logs), nothing else
257
+ tls_scan.py Accepted TLS versions and cipher suites (hand-built ClientHello)
258
+ ct.py Subdomains from Certificate Transparency logs, takeover-prone providers
259
+ hosting.py Platform recognition and platform-specific fixes
260
+ smtp_scan.py Mail servers: STARTTLS, TLS and certificate on port 25
261
+ summary.py Summary for decision makers: risks, effort, impact
262
+ branding.py Branded reports (logo, company, contact, client, colour)
263
+ software.py Outdated software: CMS and PHP recognition, support dates
264
+ trackers.py Trackers before consent (page received, or real browser)
265
+ timeline.py Grades over time (history file of each domain)
266
+ nis2.py NIS 2 view: checks related to the measures of article 21(2)
267
+ email_checks.py MX / SPF / DMARC / DKIM / MTA-STS analysis
268
+ dns_checks.py DNSSEC, CAA, registration and subdomain analysis
269
+ web_checks.py Website checks (Mozilla HTTP Observatory tests)
270
+ tls_checks.py Certificate and TLS checks
271
+ scoring.py Grades: Observatory algorithm, own scales, overall grade
272
+ sources.py Standards, ANSSI and CNIL guides cited for each check
273
+ audit.py Orchestration
274
+ report.py HTML report, summary page for several domains, console summary, JSON
275
+ pdf_report.py PDF report (optional: needs ReportLab)
276
+ history.py Changes since the previous audit of a domain
277
+ i18n.py, locales/fr.py Report languages and the French translation
278
+ demo.py Fictitious data for the demo
279
+ hsts_preload.py HSTS preload list (download, cache, lookups)
280
+ rdap.py Domain registration data (RDAP servers, answers)
281
+ observatory.py Test-by-test comparison with the Observatory API
282
+ tests/ 1034 unit tests + live tests (tests/live)
283
+ scripts/live_summary.py Audits a list of domains for the Live check workflow
284
+ scripts/observatory_compare.py Compares the website grades with the Observatory
285
+ ```
286
+
287
+ Collection is kept separate from analysis: every rule can be tested on fixed data, without depending on the Internet.
288
+
289
+ ## Tests
290
+
291
+ ```bash
292
+ python -m pytest -q # 1034 unit tests, no network needed
293
+ ruff check . # lint
294
+ mypy # type check
295
+ python -m pytest -m live -v # live tests against real domains (Internet required)
296
+ ```
297
+
298
+ Three GitHub Actions workflows keep the tool honest:
299
+
300
+ - **Tests** runs on every push: the unit tests on Python 3.10 to 3.13, the lint and type checks, a build of the package installed in a clean environment and checked for PyPI (`twine check`), and a build of the Docker image that runs the demo.
301
+ - **Release** publishes a GitHub release with the package attached, the Docker images on the GitHub Container Registry and, once set up, the package on PyPI.
302
+ - **Live check** runs every Monday (and on demand) against real domains:
303
+ - hosts from [badssl.com](https://badssl.com), deliberately misconfigured for testing (expired, self-signed and wrong-host certificates), must be flagged as critical;
304
+ - for github.com, google.com, cloudflare.com and wikipedia.org, the SPF and DMARC records read by the tool must match those returned by an independent resolver (Google DNS-over-HTTPS);
305
+ - a subdomain (docs.github.com) must inherit its parent's DMARC policy;
306
+ - DNSSEC must match an independent resolver, and CAA (google.com) and MTA-STS (gmail.com) must be found where they are published;
307
+ - registration data and security.txt must be found on known domains (including a .fr domain);
308
+ - TLS 1.0 (tls-v1-0.badssl.com), 3DES (3des.badssl.com) and the RSA key exchange (static-rsa.badssl.com) must be detected, and github.com must pass the TLS checks;
309
+ - the support dates of PHP, WordPress, Drupal, Joomla and TYPO3 must be read from endoflife.date, and docs.github.com must pass the subdomain checks;
310
+ - gmail.com's mail server must offer STARTTLS with a valid certificate (skipped when port 25 is blocked from the network running the tests);
311
+ - google.com's CAA must allow the authority of its certificate, gmail.com's DKIM keys must be read, and www.wikipedia.org loaded in a real browser must show no tracker;
312
+ - subdomains of afnic.fr must be read from the Certificate Transparency logs (the test is skipped when crt.sh and Cert Spotter are both down), and the DNS host and email provider of known domains must be recognised;
313
+ - the website grade of each domain is compared with Mozilla HTTP Observatory, test by test, and a warning is raised if the Observatory moves to another version of its scoring algorithm (this tool reproduces version 6).
314
+
315
+ The results table is published in the workflow summary and the HTML reports are kept as artifacts.
316
+
317
+ ## Publishing
318
+
319
+ **Docker images** need nothing more: the Release workflow pushes them to `ghcr.io/ryarkhewas/domain-security-check`. The first time, make the package public on GitHub (profile → Packages → domain-security-check → Package settings → Change visibility).
320
+
321
+ **PyPI** uses trusted publishing: GitHub proves to PyPI which workflow is publishing, so no PyPI password or token is stored in the repository. To set it up once:
322
+
323
+ 1. Create an account on [pypi.org](https://pypi.org/account/register/) (with two-factor authentication).
324
+ 2. In your PyPI account, Publishing → add a pending publisher: project `domain-security-check`, owner `Ryarkhewas`, repository `domain-security-check`, workflow `release.yml`, environment `pypi`.
325
+ 3. In the GitHub repository, Settings → Environments → create `pypi`, then Settings → Secrets and variables → Actions → Variables → add `PUBLISH_TO_PYPI` with the value `true`.
326
+
327
+ The next release then publishes the package on PyPI too. Before that, the PyPI step is simply skipped.
328
+
329
+ ## Limits
330
+
331
+ The audit does not cover the site's applications (forms, CMS, plugins), workstations or user accounts. It gives an overview of the exposure visible from the Internet at a given date, not a security guarantee. The subdomain check only sees names that appeared in a public certificate: a subdomain that never had one (or only a wildcard certificate) stays invisible. When crt.sh does not answer, the names come from Cert Spotter, which may list fewer old certificates; the report then says so. The platform-specific fixes are starting points to adapt, checked against each provider's documentation at the time of writing; provider menus change over time.
332
+
333
+ ## Changelog
334
+
335
+ See [CHANGELOG.md](https://github.com/Ryarkhewas/domain-security-check/blob/main/CHANGELOG.md).
336
+
337
+ ## License
338
+
339
+ MIT