The web-security series treats settings as contracts with scope and failure modes. This walkthrough inspects a host you are authorized to query. The HTTP, TLS, and DNS requests are read-only observations, although they still generate logs and traffic. SSH checks read your own trust record; the deployment host may differ from the website hostname.
Use a disposable local working directory for captured output. No output shown here is a fresh measurement of www.xbcatm.com, and a passing command proves only the property it actually checks.
1. Start with the policy the edge advertises
Fetch a GET response without silently following redirects, then inspect its status and headers:
host=www.xbcatm.com
url="https://$host/"
curl -sS --max-time 20 -D headers.txt -o body.html "$url"
cat headers.txt
The repository’s intended policy includes the following illustrative fields:
Content-Security-Policy: default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; script-src 'none'; style-src 'self'; img-src 'self' data:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
script-src 'none' is a strict choice for this script-free site, not a universal recipe for every static site. The browser enforces CSP; a report-only header does not block execution. Read the complete policy for more-specific script directives, and compare normal pages and error routes. nosniff constrains MIME handling, notably script/style destinations, but does not certify the body’s media type.
HSTS is learned from valid HTTPS responses and applies to the host that sent it. A policy received from www.example.com with includeSubDomains covers descendants of that name, not its sibling api.example.com or parent example.com. Inventory the actual scope before adopting a long duration. A proxy adding or stripping headers changes the browser contract, which is why artifact checks and edge checks are separate.
2. Verify TLS identity before reading certificate dates
SNI selects the intended virtual host; it does not verify the certificate name. Make verification failure fatal and explicitly request hostname validation. Use an up-to-date trust store appropriate to the clients under test.
openssl s_client -connect "$host:443" -servername "$host" \
-verify_hostname "$host" -verify_return_error -showcerts \
</dev/null > tls.txt 2> tls.log
# Continue only if the command succeeded; preserve tls.log on failure.
openssl x509 -in tls.txt -noout -subject -issuer -dates -ext subjectAltName
Run these as separate checked steps, or under a shell setup that stops on failure. Hiding stderr or piping into a successful x509 command can conceal an unsuccessful handshake. Inspect the DNS names in subjectAltName; the displayed subject CN is not sufficient for modern service-identity validation. Some library versions retain legacy CN fallback, so explicitly check that the expected SAN is present. A valid matching wildcard may be legitimate; an unrelated certificate is not.
notAfter gives the expiry time. It does not prove the chain is trusted, the hostname matches, or revocation was checked. Record the date and the measured safety window rather than relying on memory. OpenSSL’s result describes its configured trust store and checks, not every browser’s behavior.
3. Read the presented chain without inventing a fixed count
The -showcerts output contains certificates sent by the server, not a declaration that they form a verified chain. This command displays their count only:
grep -c 'BEGIN CERTIFICATE' tls.txt
There is no required count of four. The server normally supplies the leaf and necessary intermediate certificates; a root usually belongs in the client’s trust store and need not be sent. Chain length varies with the CA and chosen path. One certificate warrants investigating whether intermediates are needed, but a count alone is neither success nor failure.
Use the actual verification result from step 2, then investigate issuer/SAN changes, missing intermediates, expiry, and trust-store differences. An intermediate rotation can be normal CA maintenance. A numerically unchanged chain can still be wrong, and the count command does not automatically compare anything with an expected value.
4. Revalidate the captured representation
Caching is a separate contract, not evidence of TLS identity. Capture a current ETag and use it on a request selecting the same representation, as in the HTTP walkthrough. A matching conditional GET can return 304; a deployment between requests can legitimately produce 200. An old hardcoded validator is not a reusable test fixture for a changing site.
A no-script policy does not mean CSP never changes. Security headers can change independently of HTML, and a body ETag does not fingerprint every response header. Record the actual policy again after release.
5. Find the relevant CAA policy
CAA constrains issuance by compliant certificate authorities. It is not certificate pinning and does not retroactively invalidate an already issued certificate. Start by inspecting the queried name and its ancestors; retain response status so a DNS failure is not mistaken for an empty answer.
dig www.xbcatm.com CAA +noall +comments +answer
dig xbcatm.com CAA +noall +comments +answer
An empty answer at www does not mean issuance is unrestricted. RFC 8659 searches upward until the first relevant nonempty CAA set, following aliases during each lookup; continue through applicable ancestors if necessary. Inspect issue, and issuewild when wildcard issuance is involved. An iodef report address alone does not restrict issuance. If no restricting policy applies, CAA adds no issuer restriction, but normal domain validation and CA rules still apply.
Compare the CA’s documented CAA identifier with the relevant policy, not merely the leaf’s human-readable issuer CN. Today’s CAA result is evidence about today’s DNS policy, not proof of what existed when the certificate was issued.
6. Check the deployment host’s recorded SSH identity
Use the actual SSH destination, including the bracketed host/port form for a non-default port. ssh-keygen -F can find hashed known-host entries that a text grep of the hostname misses.
deploy_host=deploy.example.test
ssh-keygen -F "$deploy_host" -f "$HOME/.ssh/known_hosts" > host-record.txt
ssh-keygen -lf host-record.txt
Compare the fingerprint with an independently trusted record, such as the provider console or a verified administrator channel. This reads the local record; it does not prove which key the remote host presents now. A network key scan alone would not authenticate that key either. Investigate a changed key before updating trust; a rebuild is one possible explanation, not automatic permission to accept it.
7. Keep cookie scope no broader than necessary
Inspect all Set-Cookie fields in the captured response and any relevant redirect or application route. No cookie on one page does not establish that the entire site never sets cookies. A host-only cookie omits Domain; adding a parent Domain broadens the hosts receiving it. Path controls delivery but is not a security boundary against same-origin code.
Secure restricts transport, HttpOnly blocks script access to the cookie, and SameSite controls specified cross-site sending behavior. They address different risks and do not replace one another; SameSite=None requires Secure in modern browsers. For this site’s no-account content model, ask why a cookie is needed at all before broadening its scope. Redact cookie values from shared audit records.
Keep a bounded audit record
Record the UTC time, client versions, requested/final URL, status, enforcing headers, TLS verification result and dates, relevant CAA lookup, and SSH record source. Distinguish observed success, unexpected results, and checks not performed. These several observations take more than “two commands” and cannot prove the whole service secure.
Repeat the relevant checks after certificate, DNS, proxy, or deployment changes. Preserve prior results to identify drift, and test actual browser behavior separately when the claim depends on browser enforcement.
Sources
WHATWG Fetch Standard, nosniff processing and request destinations, checked 2026-10-15.
OpenSSL
s_client, SNI, hostname verification, fatal verification errors, and presented chains, checked 2026-10-15.RFC 9525: Service Identity in TLS, SAN-based service identity and wildcard matching, checked 2026-10-15.
RFC 8659: CAA, relevant record-set search and issuance policy, checked 2026-10-15.
RFC 6797: HSTS, host scope and policy processing, checked 2026-10-15.
OpenSSH
ssh-keygen, known-host lookup and fingerprint display, checked 2026-10-15.W3C: CSP Level 3, enforcing versus report-only policy and script directive fallback (working draft), checked 2026-10-15.
MDN: Set-Cookie, cookie attributes and their limits, checked 2026-10-15.