The external attack-surface checklist — 25 items to verify in one afternoon
Published May 17, 2026 · A practical walkthrough, not a marketing brochure. Each item has a how-to. CyberScore automates most of them — we'll say which.
Most security checklists you can download today run to 200 items, were written by SOC managers for organisations with a security team, and are unimplementable inside a 25-person SaaS without a dedicated security engineer. They produce guilt, not progress.
This one is 25 items. A technically literate non-specialist — the CTO of a small SaaS, a founder who still touches infra, a sysadmin wearing a security hat — can run through the full list in one focused afternoon. About half the items are free CLI commands you already have installed (dig, curl, nmap); the other half rely on the free tier of a named SaaS (SSL Labs, securityheaders.com, Have I Been Pwned, MXToolbox, dnstwist.it, Hardenize).
Each item follows the same template — what to check, why it matters, how to check, what good looks like. We also tag each item [Auto] if CyberScore automates it for you, or [Manual] if it genuinely needs a human in the loop. Roughly 20 of the 25 are [Auto]; the rest involve judgement calls a scanner cannot make for you.
Open a terminal, a browser tab, and a notes file. Work through the list in order — the grouping is intentional, each category builds on the previous one.
Category 1 — DNS and domains (items 1-5)
1. Subdomain enumeration [Auto]
Why it matters: the attack surface you do not know about is the one that gets you. Forgotten staging servers and ex-employee side projects are the canonical entry point.
How to check: pull from certificate transparency logs and a passive resolver.
curl -s "https://crt.sh/?q=%25.example.com&output=json" \ | jq -r '.[].name_value' | sort -u assetfinder --subs-only example.com | sort -u
Good looks like: the resulting list is at most as long as you expected. Any subdomain you cannot explain is a finding — trace ownership and decommission or document.
2. Domain typosquatting check [Auto]
Why it matters: attackers register lookalike domains (exarnple.com, example-support.com) for phishing and brand impersonation. You want to know before they hit inboxes.
How to check: paste your domain into dnstwist.it or run the CLI tool locally.
Good looks like: no registered lookalikes serving live content or MX records. Parked-but-empty is tolerable; active web or mail is a real issue.
3. SPF record present and not too permissive [Auto]
Why it matters: without SPF, any host on the internet can send mail claiming to be you. A permissive ?all or missing record is equivalent to no policy.
How to check:
dig TXT example.com +short | grep spf1
Good looks like: a single record ending in -all (hard fail) or, as an acceptable transitional state, ~all (soft fail). Anything ending in ?all or no SPF at all is a finding.
4. DMARC policy strict enough [Auto]
Why it matters: SPF and DKIM tell receivers how to authenticate; DMARC tells them what to do on failure. Without a policy stricter than p=none, failed mail still gets delivered.
How to check:
dig TXT _dmarc.example.com +short
Good looks like: p=quarantine or p=reject, with rua= pointing at a mailbox somebody actually reads. The full progression is in our DMARC, SPF and DKIM guide.
5. DKIM key length 1024+ bits, ideally 2048 [Auto]
Why it matters: DKIM signs every outgoing message. A short (512-bit) key is breakable; a missing key means no integrity protection on your mail.
How to check: use the DKIM lookup on mxtoolbox.com with your domain and the selector your provider uses (google, selector1, etc.).
Good looks like: a valid signature, key length 1024 bits at minimum, 2048 bits preferred.
Category 2 — TLS and certificates (items 6-10)
6. TLS Labs grade [Auto]
Why it matters: the single most-cited external metric for TLS posture. Auditors, customers and prospects all check it.
How to check: run ssllabs.com/ssltest against your apex domain.
Good looks like: A or A+. B is acceptable transitionally; anything C or below is a finding.
7. Certificate expiration more than 30 days away [Auto]
Why it matters: an expired cert is a self-inflicted outage. Auto-renewal is the default in 2026, but anything not on a renewal pipeline drifts silently.
How to check:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -dates
Good looks like: notAfter at least 30 days out, ideally with proof of an auto-renewal job (cron, Certbot timer, ACM, Cloudflare).
8. No deprecated protocols (TLS 1.0 and 1.1 disabled) [Auto]
Why it matters: TLS 1.0 and 1.1 have been deprecated by every major browser since 2020 and are flagged by PCI DSS, ISO 27001 audit checklists, and most enterprise procurement questionnaires.
How to check:
nmap --script ssl-enum-ciphers -p 443 example.com
Good looks like: only TLSv1.2 and TLSv1.3 negotiable. No TLSv1.0 or TLSv1.1 sections in the output.
9. Weak ciphers disabled (3DES, RC4 off) [Auto]
Why it matters: same scan as item 8 but checking the cipher list. RC4 and 3DES are broken; export- grade ciphers should not exist on anything internet-facing.
How to check: same nmap output as item 8. Look at the grading column (grade A/B/C/D/E/F) per cipher.
Good looks like: no lines flagged as weak or worse than grade C. If you see RC4 or 3DES, that is a finding regardless of grade.
10. HSTS header present, ideally preloaded [Auto]
Why it matters: HSTS tells browsers to refuse plain HTTP for your domain, killing SSL-stripping attacks at the user-agent level.
How to check:
curl -sI https://example.com | grep -i strict-transport-security
Good looks like: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload. If you want full belt-and-braces, submit to the HSTS preload list at hstspreload.org.
Category 3 — HTTP and web app (items 11-15)
11. Security headers [Auto]
Why it matters: CSP mitigates XSS, X-Frame-Options blocks click-jacking, X-Content-Type-Options stops MIME sniffing, Referrer-Policy controls header leakage. Cheap fixes, real defence-in-depth.
How to check: run securityheaders.com against your apex domain.
Good looks like: B minimum, A or A+ ideal. CSP is the hardest one to get right; if you cannot ship a strict CSP, ship a report-only one and start tightening from the violations.
12. Admin paths not publicly exposed [Manual]
Why it matters: a publicly-reachable /phpmyadmin or /.env is a self-pwn. Even returning a 200 with a login screen invites credential stuffing.
How to check: probe the obvious paths.
for p in /admin /phpmyadmin /.git /.env /backup.zip /wp-admin /server-status; do
echo -n "$p -> "; curl -sI -o /dev/null -w "%{http_code}\n" https://example.com$p
doneGood looks like: 404 or 403 on every probe. If you genuinely need an admin path online, IP-allowlist it or put it behind a Cloudflare Access / Tailscale-style auth proxy.
13. CORS not too permissive [Auto]
Why it matters: a wildcard Access-Control-Allow-Origin: * combined with credentialed requests is a recipe for cross-origin data theft.
How to check:
curl -sI -H "Origin: https://attacker.example" https://example.com/api/me \ | grep -i access-control
Good looks like: Access-Control-Allow-Origin either echoes a vetted origin from an allowlist or is absent for credentialed endpoints. Never * on anything that returns sensitive data.
14. Cookies have HttpOnly, Secure, SameSite [Auto]
Why it matters: HttpOnly stops JS from reading session cookies (XSS mitigation); Secure forces HTTPS-only transport; SameSite blocks most CSRF.
How to check: open DevTools → Application → Cookies on a logged-in page. Look at the session cookie row.
Good looks like: all three flags set on session cookies. CSRF tokens and auth cookies in particular should never lack any of them.
15. Robots.txt does not leak admin paths [Auto]
Why it matters: a classic mistake — listing /admin in a Disallow: tells search engines to skip it, and tells attackers exactly where to look.
How to check: visit https://example.com/robots.txt.
Good looks like: nothing in there you would not want indexed. If a path is sensitive, authenticate it; do not document it in robots.txt.
Category 4 — APIs and code leaks (items 16-20)
16. Swagger / OpenAPI not live in prod [Auto]
Why it matters: a publicly-accessible API documentation page lists every endpoint, parameter and schema — including the ones an attacker should not know exist. Great for staging, terrible for production.
How to check: probe the standard paths.
for p in /swagger /swagger-ui.html /api-docs /openapi.json /redoc /docs; do
echo -n "$p -> "; curl -sI -o /dev/null -w "%{http_code}\n" https://example.com$p
doneGood looks like: 404 on each, or 401/403 behind authentication. See our companion piece on BOLA and API authorisation flaws for the why.
17. GraphQL introspection disabled in prod [Auto]
Why it matters: same class of leak as Swagger but for GraphQL. Introspection lets a client query the full schema, which is great in development and terrible in production.
How to check:
curl -X POST https://example.com/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{__schema{types{name}}}"}'Good looks like: an error response saying introspection is disabled, or a generic 403. A full schema dump is a finding.
18. No public GitHub secrets [Auto]
Why it matters: credentials accidentally committed to public repos are the highest-EV finding for a budget attacker. Hours-to-minutes from commit to exploitation.
How to check: search GitHub for your domain combined with common secret strings — "example.com" api_key, "example.com" password, "example.com" AKIA. Repeat for your company name and product names.
Good looks like: zero matches with real values. Decoy strings, example configs, and explicitly-public keys are fine; anything that looks like a live secret is a P0.
19. Have I Been Pwned for company email domain [Auto]
Why it matters: employees reusing work emails on third-party services that get breached is the dominant credential-stuffing vector. You want to know which accounts are already in dumps.
How to check: the domain search at haveibeenpwned.com/DomainSearch (free for verified domain ownership).
Good looks like: zero pwned employee accounts in the last 12 months, or a remediation plan (forced password reset, MFA enforcement) for any that show up.
20. No leaked S3 buckets [Auto]
Why it matters: an open S3 bucket containing customer data is one of the highest-severity findings in the entire category. Common pattern: forgotten backup bucket, public-by- mistake.
How to check: try the obvious naming guesses.
for name in example example-backup example-prod example-staging example-uploads; do echo -n "$name -> " curl -sI "https://$name.s3.amazonaws.com" | head -1 done
Good looks like: NoSuchBucket or AccessDenied (403) on every guess. A 200 OK with a bucket listing is the worst single outcome on this list.
Category 5 — Operational hygiene (items 21-25)
21. security.txt published [Auto]
Why it matters: standardised channel for responsible disclosure (RFC 9116). Without it, an honest reporter finds it harder to reach you than a malicious one does.
How to check: visit https://example.com/.well-known/security.txt.
Good looks like: file exists, contains at minimum Contact: (mailto or HTTPS URL) and Expires: (a date within the next 12 months).
22. Privacy policy current and dated [Manual]
Why it matters: a privacy policy from 2019 is a regulatory and trust liability. GDPR, customer procurement reviews and SOC 2 all assume currency.
How to check: visit /privacy or your equivalent.
Good looks like: visible "last updated" date within the last 12 months; mentions any new tools or sub-processors you have added recently.
23. Default credentials nowhere [Manual]
Why it matters: admin/admin on a single forgotten panel is enough to ruin your week. Default creds remain the single most-exploited weakness in the CISA top-15 year after year.
How to check: list every admin panel you have (router, NAS, CI runner, monitoring stack, internal wiki) and try the defaults from the vendor docs. Yes, manually.
Good looks like: zero panels accept the defaults. Bonus points for MFA on the lot.
24. WAF or CDN in front of public origin [Manual]
Why it matters: a CDN/WAF absorbs L7 attacks, hides your origin IP, and gives you rate-limiting knobs without rewriting application code.
How to check:
curl -sI https://example.com | grep -i -E "server|cf-ray|x-amz-cf-id|x-served-by"
Good looks like: a Server: cloudflare (or Fastly, CloudFront, Akamai) header, or a generic redacted server string. A raw nginx/1.18.0 is fine functionally but tells attackers exactly what to fingerprint.
25. Monitoring and alerting on cert + score drift [Manual]
Why it matters: running the checklist once is a snapshot; value compounds from running it continuously and being alerted on regressions. Cert expiration is the highest-EV alert in the bunch — it converts a near-miss into a non-event.
How to check: do you have a job (cron, SSL Labs reminder, your APM, CyberScore alerting) that pings a human before a cert hits expiry or the external score drops by more than 10 points?
Good looks like: yes, and a human acknowledged the last alert. An alert nobody reads is worse than no alert because it manufactures false confidence.
How to prioritise if you only have one hour
The full 25 take an afternoon. If you have only sixty minutes, the highest return on time invested is in five specific items:
- Item 3 (SPF) and item 4 (DMARC) — fifteen minutes of DNS work that stops anyone on the internet sending mail in your name.
- Item 6 (SSL Labs) — free, public, scriptable. Surfaces cheap TLS misconfigurations in two minutes.
- Item 11 (security headers) — closes the most common browser-side attack vectors with a few config lines.
- Item 20 (S3 buckets) — a single open bucket is one of the worst incidents you can have. Worth the ten minutes of guesswork.
Fix those five, push to production, schedule the remaining twenty for next sprint. The marginal value of items 21-25 is real but lower than the first five.
How CyberScore automates this
Twenty of the twenty-five items above are automated in our scanner — every item tagged [Auto]. CyberScore runs them passively against every domain you add, weekly, and tells you when something drifts.
The five items we cannot automate ([Manual]) genuinely need a human:
- Item 12 (admin paths) — we can detect that a path responds, but distinguishing "exposed by mistake" from "intentionally behind Cloudflare Access" requires your context.
- Item 22 (privacy policy) — meta-check on content currency, not a network signal.
- Item 23 (default credentials) — we are passive, so we never try logins.
- Item 24 (WAF presence) — we report what the server header says, but whether you need a WAF is a business call.
- Item 25 (monitoring stack) — your alerts go wherever you wire them; we provide a webhook and an email digest.
A free scan covers items 1-2-3-4-5-6-7-8- 9-10-11-13-14-16-17-18-19-20 in about two minutes, no signup required. The output is the same per-pillar breakdown described in our methodology page.
Frequently asked questions
How long does this 25-item external attack-surface checklist take?+
For a single primary domain, a technically literate non-specialist can finish the full list in three to four focused hours. About half of the items are one-liner CLI commands (dig, curl, nmap); the other half are free-tier checks on named services (SSL Labs, securityheaders.com, Have I Been Pwned). The first run takes longer because you also have to decide what "good" means for your context; subsequent runs are routine.
Do I need a security engineer to run this checklist?+
No. The list is explicitly built for non-specialists — CTOs of small SaaS companies, founders, sysadmins. The commands are standard. The pass criteria are explicit. The judgement calls (what counts as an intentional admin path, what should be in robots.txt) are documented inline. If you can read a curl response, you can run this checklist.
Which 5 items should I do first if I only have an hour?+
Items 3 (SPF), 4 (DMARC), 6 (SSL Labs grade), 11 (security headers), and 20 (leaked S3 buckets). These five give the highest reduction in attack surface per minute spent. SPF and DMARC stop email spoofing in your name; SSL Labs surfaces the cheapest TLS misconfigurations; security headers close common browser-side attack vectors; and a leaked S3 bucket is one of the worst single failures you can have.
Does CyberScore replace this checklist?+
For 20 of the 25 items, yes — those are the ones marked [Auto] in the article. CyberScore runs them passively against every domain you add, weekly, and reports drift. Five items genuinely need a human: knowing which admin paths are intentional vs accidental, judging whether your privacy policy is current, trying default credentials on your panels, deciding whether WAF presence is a hard requirement for you, and wiring alerts into your own monitoring stack.
How often should I re-run this checklist?+
Quarterly is the realistic minimum for manual runs. Weekly is the right cadence for the [Auto] items, which is why we automated them — most external attack-surface findings appear in the deltas between scans, not in the absolute state. The Manual items (privacy policy currency, admin path intent, monitoring stack health) align well with quarterly business reviews; the rest you should not be doing by hand if you can avoid it.
Stop running this checklist manually every quarter
CyberScore runs items 1 through 20 of this checklist every week on every domain you add, and surfaces drift before it hits production. The five manual items stay manual — we make sure you only have to think about them.
Related reading
- What is Attack Surface Management? — the category definition and where this checklist fits in.
- DMARC, SPF and DKIM explained (2026) — the deep dive behind items 3, 4 and 5.
- What is BOLA? — why items 16 and 17 (exposed API docs) matter even when the API itself looks fine.
- The OWASP Top 10 walkthrough — the application-security categories sitting underneath several items on this checklist.
- Our scanning methodology — exactly how CyberScore runs the
[Auto]items. - Our own security posture — we eat the dogfood. Here is the result.
Found a check you think belongs in here, or one that should be cut? Email patrick@cybersco.re and we'll update.