Skip to main content

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

CI PyPI Python License: MIT

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 false passes. When a check cannot decide (a DNS lookup failed, the local OpenSSL cannot offer TLS 1.0, a DKIM selector cannot be guessed), it says so as a WARN instead of passing.
  • Grounded in the standards. Rules follow the RFCs, the Fetch standard and OWASP guidance, and each finding explains why it matters.
  • Built for CI. Exit codes, --fail-on warn, JSON and Markdown reports, a score and grade, and a ready-made GitHub Action.
  • Light. The core has no third-party dependencies; DNS checks use the optional [dns] extra. Python 3.9 or newer.

Contents

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 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://.
--targets-file FILE More targets, one per line; blank lines and # comments are ignored.
--format text|json|markdown, --json Output format (default text).
--output FILE Write the report to FILE as UTF-8 instead of printing it.
--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 (default 10).
--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 or has no max-age 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 only the report-only header is set; or the script policy (script-src, else default-src) allows 'unsafe-inline' without a nonce or hash, 'unsafe-eval', or scripts from any host (*, https:, http:) or data: (ignored with 'strict-dynamic')
x-content-type-options missing or not nosniff
clickjacking no CSP frame-ancestors and no X-Frame-Options: DENY/SAMEORIGIN
referrer-policy set to unsafe-url missing
permissions-policy missing
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. 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 Server includes a version number, or X-Powered-By, X-AspNet-Version or X-AspNetMvc-Version is present
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, and a Set-Cookie that only deletes a cookie (Max-Age=0 or a past Expires) is ignored. 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), or a record ending in +all / all no SPF record, ?all, or no all mechanism and no redirect=
dmarc more than one DMARC record (receivers then apply no policy) no DMARC record, p=none, a missing or invalid p=, or pct= below 100
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
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 answers without ending up on https:// after redirects

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. json gives url, status, findings, score and grade (plus note when checks were skipped or ran with --insecure); several targets give {"results": [...]}. markdown is a report for tickets, pull requests and emails, with failures first:

## Web posture report: https://example.com

HTTP 200, generated 2026-10-10 15:50 UTC by web-posture-check 0.3.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 reached (DNS failure, refused, timeout), or --output could not be written

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.3.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 or --output; the action writes the Markdown report 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

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 (redirects followed) and the headers, cookies and status of the final response are checked. A second request with Origin: 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; 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 an https:// target, the target's own port for an http:// one) to see whether it ends up on HTTPS.
  • Concurrency and --insecure. Targets are scanned in a thread pool. A target scanned with --insecure gets its own unverified TLS context passed down its call chain only, so it can never affect another target's requests.

Development

pip install -e ".[dns,dev]"
python -m unittest discover -s tests -v
ruff check src tests
Module (src/webposture/) Role
cli.py Arguments, --list-checks, the loop over targets
runner.py Scanning one target: fetch, run the selected checks, exit code
fetch.py HTTP requests, redirects, retries and error messages
output.py, markdown.py Text, JSON and Markdown rendering
headers.py, cookies.py, cors.py, tls.py, transport.py, caa.py, securitytxt.py, emailauth.py The checks
score.py, findings.py Score and grade, and the Finding type

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. CI lints with Ruff, runs the suite on Python 3.9, 3.11 and 3.13, and runs the GitHub Action from the checkout, including a case that must fail. Dependabot keeps the SHA-pinned actions and the Python tooling current.

Releases

Changes are listed in CHANGELOG.md. Pushing a tag such as v0.3.0 runs release.yml: it checks the tag matches the package version, 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

Most changes are pull requests that both authors work on, so each is co-authored by both.

License

MIT, see LICENSE.

Metadata

Release files for web-posture-check 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for web-posture-check 0.3.0
File Size Uploaded
web_posture_check-0.3.0.tar.gz 52.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for web-posture-check 0.3.0
File Interpreter ABI Platform
web_posture_check-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 89.6 kB

Release files / web_posture_check-0.3.0.tar.gz

Download URL web_posture_check-0.3.0.tar.gz
Size 52.8 kB
Tags Source
SHA-256 checksum
How to use checksums
59133b06496b9bc9aaa804acf6eaf994969cc5767e01881c6637ee604f25a91f
BLAKE2b-256 checksum
How to use checksums
b78893e71c78b199e1330e2a6e92b694a50f01ad7a4f15ecbdb96ac139dca8c0
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

Release files / web_posture_check-0.3.0-py3-none-any.whl

Download URL web_posture_check-0.3.0-py3-none-any.whl
Size 36.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
60772fb4e214f5aef4d8883322659725d882912b07f4eea50ab723b1db47aa15
BLAKE2b-256 checksum
How to use checksums
82c7b95c97e7504143f1af7ea2da791f4250a7dcdbb666c06956c5c61aa05fa7
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

Release history Release notifications | RSS feed

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page