web-posture-check
Grade a website's security posture from the outside, in one command.
Security headers · cookies · CORS · TLS · CAA · security.txt · SPF · DMARC · DKIM · HTTPS redirect
pip install "web-posture-check[dns]"
web-posture-check example.com
Target: https://example.com (HTTP 200)
Score: 55/100 (grade F)
[PASS] http-status: final response is HTTP 200
[FAIL] hsts: Strict-Transport-Security header is missing
[FAIL] csp: Content-Security-Policy header is missing
...
[FAIL] tls-protocols: server accepts TLS 1.0, TLS 1.1, which are deprecated (RFC 8996)
[WARN] caa: no CAA record, so any certificate authority may issue certificates for this host
...
[FAIL] https-redirect: http://example.com/ is served over plain HTTP without redirecting to HTTPS
Why web-posture-check
- 20 checks in one run, covering the response, the TLS server and the domain's DNS: what a browser, a mail server and an attacker each see from outside. All checks.
- No silent passes. When a check cannot decide (a DNS lookup failed, the local OpenSSL cannot offer TLS 1.0, a DKIM selector cannot be guessed, plain HTTP answered with something unreadable), it says so as a WARN instead of passing.
- Grounded in the standards. Rules follow the RFCs, the Fetch, HTML and CSP standards and OWASP guidance, and each finding explains why it matters. A repeated or comma-separated header is read the way browsers read it.
- Safe to point at a hostile site. It speaks only HTTP and HTTPS, refuses redirects to other schemes, and escapes what a site sends before printing it, so a report cannot drive your terminal or add links to a pull request.
- Built for CI. Exit codes,
--fail-on warn, JSON, Markdown and SARIF reports, a score and grade, a ready-made GitHub Action, and findings in GitHub code scanning. - Light. The core has no third-party dependencies; DNS checks use the optional
[dns]extra. Python 3.9 or newer.
Contents
- Install
- Usage
- Checks
- Reports, score and exit codes
- GitHub Action and code scanning
- How it works
- Compared with other tools · Compatibility
- Development
- Releases · Contributing and security · Authors · License
Install
pip install web-posture-check # headers, cookies, CORS, TLS, security.txt, HTTPS redirect
pip install "web-posture-check[dns]" # also CAA, SPF, DMARC and DKIM (adds dnspython)
Without the [dns] extra the DNS checks are reported as skipped, not failed. To install from source, clone the repository and run pip install ".[dns]".
Usage
web-posture-check example.com # every check, text report
web-posture-check https://example.com/login --json # a specific page, JSON
web-posture-check example.com --format markdown --output report.md
web-posture-check example.com --sarif posture.sarif # text report plus a SARIF log
web-posture-check site-one.com site-two.com --targets-file clients.txt --jobs 8
web-posture-check example.com --only tls-certificate,tls-protocols,caa
web-posture-check example.com --skip spf,dmarc,dkim --fail-on warn
web-posture-check --list-checks
| Option | What it does |
|---|---|
target ... |
Domains or URLs. A bare domain is fetched over https://, and a Unicode host name in its ASCII form (bücher.example as xn--bcher-kva.example). |
--targets-file FILE |
More targets, one per line (UTF-8); blank lines and # comments are ignored. |
--format text|json|markdown|sarif, --json |
Output format (default text). |
--output FILE |
Write the report to FILE as UTF-8 instead of printing it. |
--sarif FILE |
Also write a SARIF 2.1.0 log to FILE, from the same scan. |
--sarif-location PATH |
Repository file every SARIF result points to, such as the workflow that runs the scan (a path inside the repository, with / separators). By default the path is made from the URL (example.com/login). Needs --sarif or --format sarif. |
--only NAMES, --skip NAMES |
Comma-separated check names to run or leave out. A left-out check makes no requests. |
--list-checks |
Print every check name with a short description. |
--fail-on fail|warn |
Exit 1 on any FAIL (default), or on any WARN or FAIL. |
--jobs N |
Scan up to N targets at the same time (default 4, max 16). Output keeps the input order. |
--timeout SECONDS |
Per-request timeout in seconds, above 0 (default 10). It limits each network operation, so a server that trickles data can keep a scan going longer. |
--deadline SECONDS |
Stop waiting after SECONDS for the whole run: targets still being scanned are reported as errors (exit 2) and the reports are written. Off by default. |
--retries N |
Retry a target's first request after a timeout or dropped connection (default 1, max 5). DNS and certificate errors are not retried. |
--insecure |
If a target's certificate is not trusted, still run the other checks for that target without verification. The certificate stays a FAIL. |
--dkim-selector NAME |
DKIM selector to check, repeatable. By default common provider selectors are tried. |
--version |
Print the version. |
Checks
web-posture-check --list-checks prints this list. Each check reports PASS, WARN or FAIL with a one-line reason.
| Area | Checks |
|---|---|
| Response | http-status · hsts · csp · x-content-type-options · clickjacking · referrer-policy · permissions-policy · cross-origin-isolation · x-xss-protection · information-leakage |
| Cookies and CORS | cookies · cors |
| TLS | tls-certificate · tls-protocols |
| DNS and email | caa · spf · dmarc · dkim |
| Policy and transport | security-txt · https-redirect |
Response headers: exactly when each check fails or warns
| Check | FAIL when | WARN when |
|---|---|---|
http-status |
the final response is HTTP 400 or higher, so the other findings describe an error page. 403, 429 and 503 are often bot protection blocking automated clients | |
hsts |
Strict-Transport-Security is missing, has no valid max-age, or has max-age=0 (which tells browsers to stop enforcing HTTPS); the header is malformed in a way that makes browsers ignore it (a repeated max-age or includeSubDomains, an includeSubDomains with a value, or a directive whose name or value is outside the header's grammar, such as an unquoted URL or an = with nothing after it); or the final response is plain HTTP, where browsers ignore the header |
max-age is below 6 months, or preload is set without the preload list's requirements (max-age of at least 1 year and includeSubDomains) |
csp |
Content-Security-Policy is missing or empty |
only the report-only header is set; no policy restricts scripts (no script-src or default-src); or scripts can run in a way an attacker can use: 'unsafe-inline' without a valid nonce, hash or 'strict-dynamic', 'unsafe-eval', or scripts from any host however it is written (*, http:, https:, https://*, *:443, a wildcard right under a public suffix such as *.com or *.co.uk, or just above one, such as *.amazonaws.com over anyone's bucket.s3.amazonaws.com) or from data: (host sources are ignored with 'strict-dynamic'); ws: and wss: count too, since Safari lets them match http(s) URLs as CSP3 specifies. Script elements are judged by script-src-elem and event handlers by script-src-attr where those are set, so a policy with only script-src-elem leaves event handlers and eval() unrestricted. A policy that trusts scripts by nonce (or 'strict-dynamic' with a nonce or hash) also needs base-uri, unless another policy still checks each script's URL: otherwise injected <base> markup can send its nonced script URLs to another host. A missing object-src is not reported: browsers no longer run plugins, so it cannot run script in the page |
x-content-type-options |
missing, or its first value is not nosniff |
|
clickjacking |
no CSP frame-ancestors and no X-Frame-Options DENY or SAMEORIGIN; frame-ancestors allows any website (*, http:, https:, a host * such as https://*, a wildcard under a public suffix, or ws:/wss: in Safari), which browsers apply instead of X-Frame-Options; or X-Frame-Options has a value browsers ignore, such as ALLOW-FROM |
|
referrer-policy |
the policy that applies is unsafe-url |
missing, no recognised value, or no-referrer-when-downgrade (the full URL goes to every HTTPS site) |
permissions-policy |
missing or empty | |
cross-origin-isolation |
Cross-Origin-Opener-Policy is missing or unsafe-none, Cross-Origin-Resource-Policy is missing, or any of the three headers has a value browsers do not recognise (values are case-sensitive, Cross-Origin-Resource-Policy takes no parameters, and a repeated header is one: browsers combine its values). A missing Cross-Origin-Embedder-Policy is reported but not warned about |
|
x-xss-protection |
set to 1 (with or without mode=block), which turns on the legacy XSS auditor that can be abused for XS-Leaks, or set to an invalid value |
|
information-leakage |
a Server header includes a version number, or X-Powered-By, X-AspNet-Version or X-AspNetMvc-Version is present |
A header sent more than once is read the way browsers read it. Only the first Strict-Transport-Security counts (RFC 6797). Every Content-Security-Policy is enforced, including comma-separated policies in one header, so a script runs only if every policy allows it: a weakness counts when every policy that governs it has it, however each one writes it, and one protective frame-ancestors is enough. X-Content-Type-Options, X-Frame-Options and Referrer-Policy are combined and split on commas (the Fetch standard): the first X-Content-Type-Options value decides, conflicting X-Frame-Options values block framing (the HTML standard), and the last recognised Referrer-Policy value applies. The cross-origin policies are combined too, so two copies are an invalid value, while X-XSS-Protection is decided by its first character, as browsers did. Only tabs, spaces and line breaks around a value are ignored. In a CSP, policies are split at every comma and only ASCII whitespace separates sources: a directive with a character outside printable ASCII, such as a no-break space or a control character, is dropped, as browsers drop it.
Cookies and CORS
| Check | FAIL when | WARN when |
|---|---|---|
cookies |
a cookie on an HTTPS response lacks Secure; SameSite=None without Secure; a __Secure- cookie lacks Secure; a __Host- cookie lacks Secure or Path=/ or sets Domain (browsers reject all of these) |
a cookie lacks HttpOnly or SameSite |
cors |
the response reflects any Origin, or allows Origin: null, together with Access-Control-Allow-Credentials: true |
it reflects any Origin without credentials, or sends * with credentials (browsers reject that combination) |
Every Set-Cookie header is checked, including those sent by redirects on the way to the final page (a login, or www. to the bare domain), each judged by the scheme of the response that sent it. Cookies are told apart as a browser's cookie store tells them apart: by name, domain (the Domain attribute, or else the host that set the cookie) and path (the Path attribute, or else the directory of the URL that set it). A later Set-Cookie for the same cookie replaces the earlier one, and a Set-Cookie that only deletes a cookie (Max-Age=0 or a past Expires) is ignored. A Set-Cookie browsers reject changes nothing: a Domain that does not cover the host that sent it, or that is a public suffix such as com, co.uk or github.io (from the Public Suffix List; allowed only as the host's own name, which makes the cookie host-only in Chromium and Firefox, while WebKit refuses even that), or that lies above the host's registrable domain (amazonaws.com from bucket.s3.amazonaws.com), a Secure cookie sent over plain HTTP, a plain-HTTP cookie that would overlay a Secure one with the same name and an overlapping domain and path (plain HTTP to localhost or a loopback address counts as secure, as in browsers), or one refused for its flags (a broken __Host- or __Secure- prefix, SameSite=None without Secure), which is still reported as a FAIL. Host names and domains are compared in their ASCII form. These rules were checked in Playwright's Chromium, Firefox and WebKit; on Linux, WebKit keeps cookies in libsoup's store rather than Safari's. A missing HttpOnly is a warning because some cookies are meant to be read by JavaScript.
TLS
| Check | FAIL when | WARN when |
|---|---|---|
tls-certificate |
the certificate has expired or is not trusted (wrong host, self-signed, untrusted chain, not yet valid) | it expires within 14 days; renewal tooling normally renews 30 days ahead, so this usually means renewal is failing |
tls-protocols |
the server completes a TLS 1.0 or TLS 1.1 handshake (deprecated by RFC 8996) | this machine's OpenSSL cannot offer one of those versions, so support is unknown |
DNS and email (needs the [dns] extra)
| Check | FAIL when | WARN when |
|---|---|---|
caa |
no CAA record (any certificate authority may issue), no issue property, or an unknown critical tag (every CA must then refuse) |
|
spf |
more than one SPF record (receivers then ignore SPF), a record ending in +all / all, more than 10 DNS lookups once its includes and redirects are followed (RFC 7208: a permerror, so receivers ignore it), or an included domain without exactly one SPF record |
no SPF record, ?all, or no all mechanism and no redirect=; or a record needed to count the lookups could not be read. A passing finding shows the count, such as 10 of 10 |
dmarc |
more than one DMARC record (receivers then apply no policy) | no DMARC record, a policy of none, a missing or invalid p= (or sp=), or pct= below 100. A subdomain that inherits its parent's record is held to the subdomain policy sp=, or p= when there is none. The record is looked for up to the organizational domain from the Public Suffix List (example.co.uk, never co.uk) |
dkim |
no key under the common selectors, only revoked keys, or a selector given with --dkim-selector is missing or revoked. Selectors cannot be listed from outside, so this is reported as unknown, never as a failure |
CAA is looked up from the host up to its top-level domain, as certificate authorities do (RFC 8659). For an IP address or a single-label host such as localhost there is no domain to look up, so the four DNS checks are reported as skipped.
Policy and transport
| Check | FAIL when | WARN when |
|---|---|---|
security-txt |
/.well-known/security.txt is missing, not text/plain, lacks Contact or Expires, or has an invalid, expired, duplicate or more-than-a-year-away Expires (RFC 9116) |
|
https-redirect |
the http:// URL is served without a redirect, redirects to another http:// URL, or redirects to something other than https:// |
plain HTTP answers with a response that cannot be read, so the redirect could not be checked |
If nothing answers on plain HTTP at all, https-redirect passes, since nothing is served without TLS.
Reports, score and exit codes
Formats. text (default) prints one line per finding. sarif is a SARIF 2.1.0 log for GitHub code scanning and other SARIF viewers. json gives url, status, findings, score and grade (plus note when checks were skipped or ran with --insecure); several targets give {"results": [...]}, in input order. A target that could not be scanned is still there, with "status": null, "findings": [] and an "error", so the output is always one valid JSON document. markdown is a report for tickets, pull requests and emails, with failures first; a target that could not be scanned gets an Error: line instead of a table. The URL and the details are code spans, so text a site sends is shown literally and cannot add links, images, HTML or @mentions:
## Web posture report: `https://example.com`
HTTP 200, generated 2026-10-10 15:50 UTC by web-posture-check 0.4.0
**Grade F** (40/100): **3 FAIL**, **0 WARN**, 2 PASS
| Status | Check | Detail |
|---|---|---|
| FAIL | `hsts` | `Strict-Transport-Security header is missing` |
| FAIL | `csp` | `Content-Security-Policy header is missing` |
| FAIL | `https-redirect` | `http://example.com/ is served over plain HTTP without redirecting to HTTPS` |
| PASS | `http-status` | `final response is HTTP 200` |
| PASS | `tls-certificate` | `certificate valid until 2026-12-25 (76 days)` |
Score and grade. Each check counts 1 for PASS, 0.5 for WARN and 0 for FAIL, averaged to 100, so it means the same for any --only / --skip selection. A is 90 and above, B 80, C 70, D 60, F below. A needs zero FAILs: one serious problem cannot hide behind many passes. Skipped checks are shown but not scored.
Exit codes. With several targets, the worst one wins.
| Code | Meaning |
|---|---|
| 0 | No FAIL (WARN findings may exist) |
| 1 | At least one FAIL, including an untrusted certificate; with --fail-on warn, also any WARN |
| 2 | A target could not be scanned (an invalid target, a DNS failure, a refused connection, a timeout, or nothing could be fetched even with --insecure), or --output or --sarif could not be written. The other targets and reports are still written |
| 130 | Interrupted with Ctrl-C; the scans still running are abandoned |
GitHub Action
Check sites on a schedule. The job fails on problems and the Markdown report appears in the job summary.
name: Website posture
on:
schedule:
- cron: "0 6 * * 1" # Mondays 06:00 UTC
workflow_dispatch:
jobs:
posture:
runs-on: ubuntu-latest
steps:
- id: posture
uses: MohammadThabetHassan/web-posture-check@v0.4.0
with:
targets: |
example.com
shop.example.com
args: --fail-on warn
- if: always()
env:
GRADE: ${{ steps.posture.outputs.grade }}
SCORE: ${{ steps.posture.outputs.score }}
run: echo "Grade $GRADE ($SCORE/100)"
| Input | Default | Meaning |
|---|---|---|
targets |
(required) | Domains or URLs, separated by spaces or new lines |
args |
"" |
Extra options such as --only tls-certificate,caa or --fail-on warn. Do not pass --format, --json, --output, --sarif or --sarif-location; the action writes the Markdown report and the SARIF log itself |
dns |
"true" |
Install the [dns] extra for the CAA, SPF, DMARC and DKIM checks |
python-version |
"3.12" |
Python version to run with |
| Output | Meaning |
|---|---|
score |
Lowest score (0–100) across the targets |
grade |
Worst grade (A–F) across the targets |
exit-code |
The tool's exit code (see above) |
report |
Path of the Markdown report file, e.g. to attach to an issue |
sarif |
Path of the SARIF 2.1.0 log, for code scanning |
GitHub code scanning
Since v0.4.0: upload the sarif output and every FAIL and WARN becomes an alert in the repository's Security tab. Alerts open when a problem appears and close by themselves when a later scan no longer finds it.
jobs:
posture:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- id: posture
uses: MohammadThabetHassan/web-posture-check@v0.4.0
with:
targets: example.com
- if: always() && steps.posture.outputs.sarif != ''
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: ${{ steps.posture.outputs.sarif }}
category: web-posture-check
FAIL becomes an error and WARN a warning; PASS and skipped checks are not uploaded. Each rule carries a security-severity, so GitHub shows alerts as High, Medium or Low: cors 8.0 and tls-certificate 7.5 are high; hsts and https-redirect 6.5, cookies 6.0, csp, tls-protocols and dmarc 5.5, spf 5.0 and clickjacking 4.5 are medium; the rest are low. They are defaults for each check's worst case, set in src/webposture/checks.py. If the repository's code scanning check fails only on High or higher, the medium rules no longer fail it; the Action step itself still fails on any FAIL. A website has no source line, so each alert points to the workflow file that ran the scan and names the URL in its message. An alert's identity is the check and the URL, so a detail that changes between runs (such as days until a certificate expires) updates the same alert instead of opening a new one. The Action's own CI uploads its SARIF to code scanning on every change, so the format is checked against GitHub itself, and validates it against the OASIS SARIF 2.1.0 schema.
For a fully reproducible workflow, pin the action to the release's commit SHA instead of the tag. The SecuritySolution.tech website runs this action every day alongside its own posture script.
How it works
- The response. The target is fetched once and the headers and status of the final response are checked, with the cookies of every response on the way. Only
http://andhttps://are ever requested: a redirect to anything else (file:,ftp:) is refused, as are redirect loops. A second request withOrigin: https://web-posture-check.invalid, a reserved domain that cannot exist (RFC 2606), tests CORS: a site that trusts it trusts any website. - TLS. The certificate is read with the standard verifying TLS context. TLS 1.0 and 1.1 are each probed with a handshake allowed only that version; OpenSSL 3 will not offer them at its default security level, so the probe lowers it for that connection only, and if it still cannot offer one the result is a WARN, never a pass.
- DNS. CAA is read for the host, climbing to parent domains as certificate authorities do (RFC 8659). SPF, DMARC and DKIM are read for the mail domain (
www.removed); DMARC falls back to the organizational domain as receivers do, found with the Public Suffix List that ships with the package; SPF's DNS lookups are counted through its includes; DKIM is looked up at<selector>._domainkey.<domain>for Google Workspace, Microsoft 365, Mailchimp, SendGrid, Cloudflare Email Routing and common defaults, or for--dkim-selector. - Transport. The same host and path are requested over
http://(default port for anhttps://target, the target's own port for anhttp://one) to see whether it ends up on HTTPS. - Concurrency and
--insecure. Targets are scanned in a thread pool. A target scanned with--insecuregets its own unverified TLS context passed down its call chain only, so it can never affect another target's requests. ItsStrict-Transport-Securityis judged as configured, with a note that browsers ignore it until the certificate is trusted. - Output. Findings quote what a site sent. Text and Markdown reports show control characters (terminal escape sequences, bidirectional overrides) as visible escapes such as
\x1b, and Markdown puts the quoted text in code spans. JSON and SARIF escape it as JSON does.
Compared with other tools
web-posture-check does not replace the established tools below; it puts a broad, standards-based baseline of all three areas (web, TLS, email DNS) into one command that fits a pipeline. Pick by the question you are asking:
| Tool | Best at | How you run it |
|---|---|---|
| web-posture-check | One baseline across response headers, cookies, CORS, certificate, TLS 1.0/1.1, CAA, security.txt, SPF, DMARC, DKIM and the HTTPS redirect, with a score, exit codes and SARIF | CLI and GitHub Action, any number of sites, no account |
| Mozilla HTTP Observatory | Grading HTTP security headers, cookies and redirects, with detailed guidance | Website and API |
| testssl.sh | Deep TLS testing: every protocol and cipher, known TLS vulnerabilities, certificates, STARTTLS and services on any port | Bash script |
| internet.nl | Modern internet standards for websites and mail: IPv6, DNSSEC, HTTPS and headers, SPF, DKIM, DMARC, STARTTLS, DANE and RPKI | Website and batch API |
| securityheaders.com | A quick grade of a page's security headers | Website |
A good pattern is to run web-posture-check on every change or on a schedule to catch regressions, and the specialist tools when you need depth in one area.
What web-posture-check does not do: test ciphers or TLS vulnerabilities (use testssl.sh); check DNSSEC, IPv6, DANE or mail-server STARTTLS (use internet.nl), or MTA-STS, which internet.nl does not test either; crawl a site or test for vulnerabilities such as XSS or SQL injection (use a scanner such as OWASP ZAP). It looks at one URL per target from outside, as a visitor, a mail receiver or an attacker would.
Compatibility
Version 0.x, but scripts and pipelines can rely on these. A change to any of them is listed in the CHANGELOG and only made in a new minor version (0.x to 0.y), never in a patch release:
- Exit codes 0, 1, 2 and 130 and what they mean.
- Check names (
--list-checks), which are also the SARIF rule IDs and the names--onlyand--skiptake. A new check can be added in a minor version; it is announced in the CHANGELOG, since--fail-onmay then fail on it. - JSON fields:
url,status,findings(check,status,detail),score,grade,note,errorfor a target that could not be scanned, andresultsfor several targets. New fields may be added. - Statuses
PASS,WARNandFAIL, and that a skipped check starts its detail withskipped:. - Action inputs and outputs: the inputs
targets,args,dnsandpython-version, and the outputsscore,grade,exit-code,reportandsarif.
The detail text, the text and Markdown layouts and the exact score of a site can change in any release: a check that becomes more accurate can change a result. Pin a version (web-posture-check==0.4.0, or the action at a release SHA) for fully repeatable results.
Development
pip install -e ".[dns,dev]"
coverage run tests/offline.py && coverage report # every test, network blocked, branch coverage
ruff check src tests # lint
mypy # strict for the package, tests included
Module (src/webposture/) |
Role |
|---|---|
cli.py |
Arguments, --list-checks, the loop over targets |
checks.py |
Every check's name and summary, in report order |
runner.py |
Scanning one target: fetch, run the selected checks (ScanOptions), exit code |
fetch.py, headermap.py |
HTTP requests, redirects, retries and error messages; response headers as browsers read them |
output.py, markdown.py, sarif.py, textsafe.py |
Text, JSON, Markdown and SARIF rendering, and making a site's text safe to print |
headers.py, cookies.py, cors.py, tls.py, transport.py, caa.py, securitytxt.py, emailauth.py |
The checks |
publicsuffix.py |
The bundled Public Suffix List, for cookie domains, CSP wildcards and the DMARC organizational domain |
score.py, findings.py |
Score and grade, and the Finding and ScanResult types |
Checks are pure functions that take a response and return a Finding, so most tests need no network. tests/test_end_to_end.py runs the real CLI against small web servers on 127.0.0.1, tests/test_https_end_to_end.py does the same over HTTPS, and tests/test_tls_live.py runs real TLS handshakes; both use a throwaway CA made by openssl, so the certificate and protocol code is tested without mocks. No test touches the internet: tests/offline.py runs the suite with only loopback reachable and fails it if any test tries more.
CI on every change:
- Ruff lint and mypy, strict for the package and checking every function body of the tests.
- The test suite, with the network blocked, on Python 3.9 to 3.15 on Linux and on Windows and macOS. Branch coverage must stay at 99% or more (100% today); each Linux run puts the coverage table in its job summary.
- The built wheel and source archive, checked with
twineand installed into clean environments with and without the[dns]extra. - CodeQL static analysis of the Python code and the workflows (security-extended queries).
- The GitHub Action run from the checkout: a passing case, a case that must fail, and its SARIF uploaded to code scanning and validated against the OASIS schema.
Every action in the workflows is pinned to a commit SHA, no checkout keeps the job's token, and Dependabot keeps the actions and the Python tooling current, proposing a new release only after it has been out for a week.
Releases
Changes are listed in CHANGELOG.md. Pushing a tag such as v0.4.0 runs release.yml: it checks the tag matches the package version, runs the test suite, builds and checks the package, and publishes it to PyPI with Trusted Publishing, so no PyPI token exists anywhere. Every published file carries a PyPI attestation linking it to that workflow.
Contributing and security
Contributions are welcome; see CONTRIBUTING.md. To report a vulnerability in the tool itself, follow SECURITY.md instead of opening a public issue.
Authors
- Mohammad Thabet (@MohammadThabetHassan)
- Omar Alraas (@omaralraas)
Most changes are pull requests that both authors work on, so each is co-authored by both.
License
MIT, see LICENSE. The bundled Public Suffix List (src/webposture/public_suffix_list.dat) is from publicsuffix.org under the Mozilla Public License 2.0, as its header says, so the package's licence expression is MIT AND MPL-2.0.
Metadata
Release files for web-posture-check 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| web_posture_check-0.4.0.tar.gz | 228.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| web_posture_check-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 388.8 kB
Release files / web_posture_check-0.4.0.tar.gz
| Download URL | web_posture_check-0.4.0.tar.gz |
|---|---|
| Size | 228.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
64d06b29fec9297ac3d38da065a82234afcc519a919139d82cd68e67c5df2875
|
|
BLAKE2b-256 checksum How to use checksums |
e2038fc0c0ca3378f90760f0d88d71d05cb4d7b6a64086669cc4818f58867fc0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / web_posture_check-0.4.0-py3-none-any.whl
| Download URL | web_posture_check-0.4.0-py3-none-any.whl |
|---|---|
| Size | 160.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
12ab6972de35d6dfc4bc816f8bd8728c5525a7958e3fae8b31a6bbf787a30880
|
|
BLAKE2b-256 checksum How to use checksums |
b40d5f8055ef23bfa171eace63c07012e309f90fbd703eb7674929d05831426e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency log