Domain Security Scanner: SSL, DMARC, Subdomains, Tech Stack
Pricing
from $80.00 / 1,000 full exposure reports
Domain Security Scanner: SSL, DMARC, Subdomains, Tech Stack
Passive security report for a domain: SSL/TLS, headers, SPF/DKIM/DMARC, subdomains, tech stack and known CVEs, scored 0-100 with fixes. Use this when vetting a vendor or your own domain.
Pricing
from $80.00 / 1,000 full exposure reports
Rating
0.0
(0)
Developer
mohamed senator
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Domain Security & Tech Stack Report (MCP) — SSL, DMARC, Subdomains, KEV
Use this tool when an AI agent (or a person) needs a decision-ready answer about a domain's external security posture from public data: is this vendor/partner/target domain healthy, what should be fixed first, and what changed since last week? One domain in, one compact, scored JSON report out — with a 0–100 score, an A–F grade, a prioritized machine-readable fixes array, and a sources list (URL + fetch time) behind every fact.

It is a normal pay-per-event (PPE) Actor — not Standby — so it works from the Apify MCP server (https://mcp.apify.com), the API, schedules and agent-payment flows. Everything is passive: DNS lookups, RDAP, certificate-transparency logs, one HTTPS page fetch and public vulnerability feeds. No port scanning, no brute forcing, no exploit attempts, no logins, no paywalled sources.

Modes (one tool per mode)
Set mode in the input. Each mode has its own price (see Pricing).
mode | Use it to… | Returns |
|---|---|---|
full_report | Get the whole picture in one call (due diligence, vendor risk, triage) | score + grade, fixes, DNS, RDAP, email, TLS/headers, tech, CVE/KEV/EPSS, subdomains |
tech_stack | See what a site runs | technologies with version, confidence 0–100 and the evidence that matched |
subdomains | Map the attack surface | CT-log subdomains, live/dead, takeover candidates |
email_security | Judge spoofing/phishing exposure | SPF (lookup count vs 10), DMARC, DKIM, MTA-STS, BIMI — each pass/warn/fail with a fix |
tls | Check transport security | chain validity, issuer, days to expiry, protocol, HSTS/CSP/other headers, security.txt |
diff | Monitor on a schedule | only what changed since the last run: subdomains, certificate, tech, new KEV matches, email policy |
Vulnerability matching (vulns) is part of full_report and diff; it is never charged separately.
Calling it from an MCP client
{ "tool": "<your-username>/domain-exposure-intel", "arguments": { "mode": "email_security", "domain": "example.com" } }
Bulk: pass domains: ["a.com", "b.com", …] (max 100). You get one dataset item per domain and each item is charged separately — only when it contains data.

Output at a glance
Every dataset item has:
outcome—found|no_match|unavailable|error. Onlyfoundis ever charged.summary— one agent-readable paragraph.score—{ value 0-100, grade A-F, coverage[], breakdown[] }where a score applies. Categories that could not be evaluated are left out of the score, not counted as zero.fixes[]—{ id, priority (critical|high|medium|low), area, title, action, evidence, impact }, critical first.sources[]—{ url, fetchedAt }for every fact (each section also carries its ownsources).billing—{ event, priceUsd }(orevent: nullfor free results).
The examples below were produced from the project's offline test fixtures for the fictional domain
acme.example. They show the exact shape of real output (long arrays are shortened with"..."), not measurements of a real site.
full_report — $0.08
Input: { "mode": "full_report", "domain": "acme.example" }
{"domain": "acme.example","apex": "acme.example","mode": "full_report","outcome": "found","summary": "acme.example: exposure score 64/100 (grade D). 2 urgent fixes: jQuery 1.12.4 (detected) matches 2 known vulnerabilities, 1 in CISA KEV; Possible subdomain takeover: old-shop.acme.example; TLS cert valid, 54d to expiry, TLSv1.3; SPF pass (1/10 lookups), DMARC pass (p=quarantine); stack: WordPress 6.3.1, PHP 7.4.3, nginx 1.18.0, jQuery 1.12.4, Bootstrap 4.3.1; 8 CVEs match detected versions (1 in CISA KEV); 7 subdomains (6 live of 7 resolved), 2 takeover candidates.","checkedAt": "2026-10-06T12:00:00.000Z","score": {"value": 64,"grade": "D","coverage": ["email_security","tls_transport","security_headers","known_vulnerabilities","subdomain_hygiene","dns_and_registration"],"breakdown": [{"category": "email_security","points": 20,"max": 20},{"category": "tls_transport","points": 17,"max": 17},{"category": "security_headers","points": 5,"max": 14},"..."]},"fixes": [{"id": "vuln-jquery","priority": "critical","area": "vulns","title": "jQuery 1.12.4 (detected) matches 2 known vulnerabilities, 1 in CISA KEV","action": "Upgrade jQuery to 3.5.0 or later, or confirm the version and any vendor backports.","evidence": "KEV: CVE-2020-11022; max EPSS 23.1%","impact": 10.5},{"id": "subdomain-takeover-old-shop.acme.example","priority": "high","area": "subdomains","title": "Possible subdomain takeover: old-shop.acme.example","action": "Remove the dangling DNS record for old-shop.acme.example (CNAME oldshop.azurewebsites.net) or re-create the Azure App Service resource under your control.","evidence": "CNAME target oldshop.azurewebsites.net does not exist (NXDOMAIN); Azure App Service names can often be re-registered. Verify manually; do not claim.","impact": 6},{"id": "headers-csp","priority": "medium","area": "headers","title": "Content-Security-Policy missing or weak","action": "Add a Content-Security-Policy (start with default-src 'self' in report-only mode).","evidence": "No Content-Security-Policy header.","impact": 4},{"...": "10 more fixes"}],"sources": [{"url": "dns://acme.example?type=A,AAAA,MX,NS,TXT,CAA","fetchedAt": "2026-10-06T12:00:00.000Z"},{"url": "https://rdap.org/domain/acme.example","fetchedAt": "2026-10-06T12:00:00.000Z"},{"url": "dns://acme.example?type=MX,TXT","fetchedAt": "2026-10-06T12:00:00.000Z"},{"...": "21 more"}],"dns": {"status": "ok","a": ["93.184.216.34"],"mx": [{"priority": 10,"exchange": "mail.acme.example"}],"ns": ["ns1.acme.example","ns2.acme.example"],"caaPresent": true,"...": "txt, caa, sources"},"whois": {"registrar": "Example Registrar, Inc.","createdAt": "2016-03-14T10:12:00Z","expiresAt": "2027-03-14T10:12:00Z","daysToExpiry": 158,"transferLocked": true},"email": {"handlesMail": true,"spf": {"status": "pass","detail": "SPF present, 1/10 lookups, ends in -all."},"dmarc": {"status": "pass","detail": "DMARC enforced with p=quarantine and reporting enabled."},"dkim": "...","mtaSts": "...","bimi": "..."},"tls": {"certificate": {"issuer": "C=US, O=Let's Encrypt, CN=R11","validTo": "2026-11-30T09:00:00.000Z","daysToExpiry": 54,"chainValid": true,"status": "pass"},"protocol": {"negotiated": "TLSv1.3","cipher": "TLS_AES_256_GCM_SHA384","status": "pass","detail": "TLS 1.3 negotiated."},"headers": {"hsts": {"status": "warn","detail": "HSTS lacks includeSubDomains."},"csp": {"status": "fail"},"...": ""},"securityTxt": {"found": true,"status": "pass"}},"technologies": [{"name": "WordPress","version": "6.3.1","confidence": 98},{"name": "PHP","version": "7.4.3","confidence": 93},{"name": "nginx","version": "1.18.0","confidence": 85},{"name": "Google Analytics","version": null,"confidence": 83},"..."],"subdomains": {"totals": {"discovered": 7,"resolved": 7,"live": 6,"dead": 1,"wildcardNames": 1},"subdomains": [{"name": "api.acme.example","status": "live","ips": ["93.184.216.40"]},{"name": "blog.acme.example","status": "live","ips": ["178.128.137.63"],"cname": "acme.ghost.io"},"..."],"takeoverCandidates": [{"subdomain": "docs.acme.example","cname": "acme-docs.github.io","service": "GitHub Pages","label": "candidate","evidence": "http_fingerprint","confidence": "medium","note": "GitHub Pages page for an unclaimed resource is served at docs.acme.example. Verify manually; do not claim."},"..."]},"vulnerabilities": {"totals": {"findings": 8,"kev": 1,"epssAtLeast10pct": 1,"critical": 0,"high": 3},"findings": [{"id": "CVE-2020-11022","technology": "jQuery","detectedVersion": "1.12.4","versionStatus": "detected","kev": true,"epss": 0.231,"cvss": null,"fixedVersion": "3.5.0"},{"id": "CVE-2019-11358","technology": "jQuery","detectedVersion": "1.12.4","versionStatus": "detected","kev": false,"epss": 0.01234,"cvss": null,"fixedVersion": "3.4.0"},"..."],"notes": ["Versions are inferred from banners and asset URLs: matches are \"detected\", not \"confirmed\". Backported patches can make a detected version safe."]}}
tech_stack — $0.015
Input: { "mode": "tech_stack", "domain": "acme.example" }
{"domain": "acme.example","apex": "acme.example","mode": "tech_stack","outcome": "found","summary": "acme.example: stack: WordPress 6.3.1, PHP 7.4.3, nginx 1.18.0, jQuery 1.12.4, Bootstrap 4.3.1.","checkedAt": "2026-10-06T12:00:00.000Z","score": null,"fixes": [],"sources": [{"url": "https://acme.example/","fetchedAt": "2026-10-06T12:00:00.000Z"}],"technologies": [{"name": "WordPress","category": "cms","version": "6.3.1","confidence": 98,"evidence": [{"type": "meta","detail": "meta generator=WordPress 6.3.1"},{"type": "html","detail": "html /wp-content/"}]},{"name": "PHP","category": "language","version": "7.4.3","confidence": 93,"evidence": [{"type": "header","detail": "x-powered-by: PHP/7.4.3"},{"type": "cookie","detail": "cookie PHPSESSID"}]},{"name": "nginx","category": "web-server","version": "1.18.0","confidence": 85,"evidence": [{"type": "header","detail": "server: nginx/1.18.0"}]},"..."],"pageUrl": "https://acme.example/"}
Versions come from headers, <meta name="generator"> and asset URLs. A technology without a visible version has version: null. The signature set (~210 entries: servers, CMSs, frameworks, CDNs, analytics, JS libraries) is curated and written for this Actor; no third-party fingerprint dataset is bundled, because the licences of the popular ones are either proprietary or copyleft and could not be cleared for commercial redistribution.
subdomains — $0.02
Input: { "mode": "subdomains", "domain": "acme.example", "maxSubdomains": 100 }
{"domain": "acme.example","apex": "acme.example","mode": "subdomains","outcome": "found","summary": "acme.example: exposure score 10/100 (grade F). 7 subdomains (6 live of 7 resolved), 2 takeover candidates.","checkedAt": "2026-10-06T12:00:00.000Z","score": {"value": 10,"grade": "F","coverage": ["subdomain_hygiene"],"breakdown": [{"category": "subdomain_hygiene","points": 1,"max": 10,"percent": 10}]},"fixes": [{"id": "subdomain-takeover-old-shop.acme.example","priority": "high","area": "subdomains","title": "Possible subdomain takeover: old-shop.acme.example","action": "Remove the dangling DNS record for old-shop.acme.example (CNAME oldshop.azurewebsites.net) or re-create the Azure App Service resource under your control.","evidence": "CNAME target oldshop.azurewebsites.net does not exist (NXDOMAIN); Azure App Service names can often be re-registered. Verify manually; do not claim.","impact": 6},"..."],"sources": [{"url": "https://crt.sh/?q=%25.acme.example&output=json&exclude=expired","fetchedAt": "2026-10-06T12:00:00.000Z"},{"...": "2 more"}],"subdomains": {"discoverySource": "crt.sh","totals": {"discovered": 7,"resolved": 7,"live": 6,"dead": 1,"wildcardNames": 1},"subdomains": [{"name": "api.acme.example","status": "live","ips": ["93.184.216.40"]},{"name": "blog.acme.example","status": "live","ips": ["178.128.137.63"],"cname": "acme.ghost.io"},"..."],"takeoverCandidates": [{"subdomain": "docs.acme.example","cname": "acme-docs.github.io","service": "GitHub Pages","label": "candidate","evidence": "http_fingerprint","confidence": "medium","note": "GitHub Pages page for an unclaimed resource is served at docs.acme.example. Verify manually; do not claim."},{"subdomain": "old-shop.acme.example","cname": "oldshop.azurewebsites.net","service": "Azure App Service","label": "candidate","evidence": "cname_target_nxdomain","confidence": "high","note": "CNAME target oldshop.azurewebsites.net does not exist (NXDOMAIN); Azure App Service names can often be re-registered. Verify manually; do not claim."}]}}
Names come from certificate-transparency logs (crt.sh JSON, falling back to Cert Spotter), are de-duplicated and resolved live/dead. A takeover candidate is a CNAME that points at a known third-party service whose target is NXDOMAIN, or whose public page shows that service's "unclaimed" fingerprint. These are labelled candidate and are never verified by claiming anything — verify manually.
email_security — $0.005
Input: { "mode": "email_security", "domain": "acme.example" }
{"domain": "acme.example","apex": "acme.example","mode": "email_security","outcome": "found","summary": "acme.example: exposure score 100/100 (grade A). SPF pass (1/10 lookups), DMARC pass (p=quarantine), DKIM pass, MTA-STS pass, BIMI not published.","checkedAt": "2026-10-06T12:00:00.000Z","score": {"value": 100,"grade": "A"},"fixes": [{"id": "email-bimi","priority": "low","area": "email","title": "BIMI not published or inactive","action": "Optional: publish default._bimi TXT with an SVG logo (needs DMARC p=quarantine/reject at pct=100).","evidence": "BIMI not published (optional brand-logo standard).","impact": 0}],"sources": [{"url": "dns://acme.example?type=MX,TXT","fetchedAt": "2026-10-06T12:00:00.000Z"},{"...": "6 more"}],"email": {"spf": {"status": "pass","lookups": 1,"qualifier": "-all","detail": "SPF present, 1/10 lookups, ends in -all."},"dmarc": {"status": "pass","policy": "quarantine","detail": "DMARC enforced with p=quarantine and reporting enabled."},"dkim": {"status": "pass","detail": "DKIM key(s) published on selector(s): google."},"mtaSts": {"status": "pass","mode": "enforce","detail": "MTA-STS enforced (max_age 604800s)."},"bimi": {"status": "warn","detail": "BIMI not published (optional brand-logo standard).","fix": "Optional: publish default._bimi TXT with an SVG logo (needs DMARC p=quarantine/reject at pct=100)."}}}
SPF lookups are counted recursively through include/redirect against the RFC 7208 limit of 10. DKIM is checked on ~26 common selectors (add yours with dkimSelectors); "no key found" is a warn, not a fail, because selectors are not enumerable.
tls — $0.005
Input: { "mode": "tls", "domain": "acme.example" }
{"domain": "acme.example","apex": "acme.example","mode": "tls","outcome": "found","summary": "acme.example: exposure score 71/100 (grade C). TLS cert valid, 54d to expiry, TLSv1.3; 2/6 security headers present; security.txt found.","checkedAt": "2026-10-06T12:00:00.000Z","score": {"value": 71,"grade": "C"},"fixes": [{"id": "headers-csp","priority": "medium","area": "headers","title": "Content-Security-Policy missing or weak","action": "Add a Content-Security-Policy (start with default-src 'self' in report-only mode).","evidence": "No Content-Security-Policy header.","impact": 4},{"id": "headers-xfo","priority": "medium","area": "headers","title": "Clickjacking protection missing","action": "Send X-Frame-Options: DENY (or CSP frame-ancestors 'none').","evidence": "No X-Frame-Options or CSP frame-ancestors.","impact": 2},"..."],"sources": [{"url": "tls://acme.example:443","fetchedAt": "2026-10-06T12:00:00.000Z"},{"url": "https://acme.example/","fetchedAt": "2026-10-06T12:00:00.000Z"},{"...": "2 more"}],"tls": {"certificate": {"issuer": "C=US, O=Let's Encrypt, CN=R11","validTo": "2026-11-30T09:00:00.000Z","daysToExpiry": 54,"chainValid": true,"status": "pass"},"protocol": {"negotiated": "TLSv1.3","status": "pass"},"httpToHttps": {"status": "pass","detail": "HTTP 301 redirect to HTTPS.","redirectsTo": "https://acme.example/"},"headers": {"hsts": {"present": true,"status": "warn"},"csp": {"present": false,"status": "fail"},"xFrameOptions": {"present": false,"status": "fail"},"xContentTypeOptions": {"present": true,"status": "pass"},"referrerPolicy": {"present": false,"status": "warn"},"permissionsPolicy": {"present": false,"status": "warn"}},"securityTxt": {"found": true,"status": "pass","contact": ["mailto:security@acme.example"],"expires": "2027-06-30T00:00:00.000Z"}}}
diff — $0.02 (first baseline free)
Input: { "mode": "diff", "domain": "acme.example" } — run it weekly with an Apify Schedule and keep the same snapshotStoreName.
- First run: stores a snapshot in a named key-value store and returns
outcome: "found"withdiff.baseline: true— free. - Later runs: return only changes. No changes →
outcome: "no_match"— free. Changes →outcome: "found"— charged. - If a data source is down during a later run, that section is skipped (listed in
diff.skippedSections) and its previous snapshot value is kept, so a flaky network never produces fake "removed" changes.
{"domain": "acme.example","apex": "acme.example","mode": "diff","checkedAt": "2026-10-06T12:00:00.000Z","outcome": "found","summary": "acme.example: 4 changes since 2026-10-06 — DMARC changed: p=quarantine -> p=none (weaker).","score": {"value": 55,"grade": "F"},"fixes": [{"id": "email-dmarc","priority": "medium","area": "email","title": "DMARC not enforced for acme.example","action": "Review aggregate reports, then move to p=quarantine and p=reject.","evidence": "DMARC policy is p=none (monitoring only); spoofed mail is not blocked.","impact": 4},"..."],"diff": {"baseline": false,"changes": [{"type": "email_dmarc_changed","severity": "high","summary": "DMARC changed: p=quarantine -> p=none (weaker)"},{"type": "subdomain_added","severity": "info","summary": "New subdomain vpn.acme.example"},{"type": "cert_renewed","severity": "info","summary": "Certificate renewed (new expiry 2027-01-10)"},{"type": "tech_version_changed","severity": "info","summary": "nginx version 1.18.0 -> 1.20.2"}],"counts": {"subdomain_added": 1,"cert_renewed": 1,"tech_version_changed": 1,"email_dmarc_changed": 1},"skippedSections": []}}
Pricing
Pay-per-event. You pay only when an item has outcome: "found". Unreachable domains, empty results, errors and "no changes" cost $0.
| Mode | Event | Price per domain |
|---|---|---|
full_report | full-report | $0.08 |
tech_stack | tech-stack | $0.015 |
subdomains | subdomain-scan | $0.02 |
email_security | email-security | $0.005 |
tls | tls-check | $0.005 |
diff | diff-report | $0.02 |
When is an item "found" (charged)?
full_report: at least 3 of the 6 base sections (DNS, RDAP, email, TLS, tech, subdomains) returned data.tech_stack: the page was fetched and at least one technology matched.subdomains: at least one subdomain was found in CT logs.email_security: DNS answered for the domain (a missing SPF/DMARC record is a finding, and is charged).tls: a TLS handshake or the HTTPS page fetch succeeded.diff: at least one change since the previous snapshot (baseline and no-change runs are free).
Spending limits. The Actor respects your run's maximum cost. When the limit is reached it stops cleanly: the item that would exceed it is not delivered or charged, remaining domains are returned as outcome: "unavailable" with stoppedReason: "max_total_charge_reached", and the run summary says so.
Input
| Field | Default | Max | Notes |
|---|---|---|---|
mode | full_report | see above | |
domain | one domain/URL (agent shortcut) | ||
domains | 100 items | bulk list | |
maxDomains | 25 | 100 | cap on processed domains |
maxSubdomains | 100 | 500 | how many CT subdomains to resolve |
includeVulns | true | CVE matching in full_report/diff | |
dkimSelectors | 20 items | extra selectors | |
snapshotStoreName | domain-exposure-snapshots | diff mode | |
updateBaseline | true | diff mode | |
maxConcurrency | 5 | 10 | |
requestTimeoutSecs | 10 | 30 | every request has a timeout and is retried with backoff |
Empty input runs a fast demo on example.com.
Data sources and etiquette
DNS (system resolver, DNS-over-HTTPS fallback), RDAP via rdap.org, crt.sh with Cert Spotter fallback, one HTTPS fetch of / (plus /.well-known/security.txt, MTA-STS policy and an HTTP→HTTPS redirect check), OSV.dev (JS libraries, Drupal, Laravel), NVD CPE matching (server software, WordPress, PHP…), the CISA KEV feed and FIRST EPSS. Vulnerability lookups are cached in a named key-value store.
- Sends a descriptive
User-Agent(DomainExposureIntelBot/…) and honoursrobots.txtfor page fetches (if a site disallows/, tech/header results areunavailable)./.well-known/standards files are fetched as machine-readable protocol endpoints. - Target hosts are checked so private/reserved addresses are never fetched (SSRF guard, re-checked on every redirect).
- TLS is a normal handshake on port 443 only; nothing else is probed.
Optional environment variables (set them as secret env vars in Actor settings; none are required): NVD_API_KEY (higher NVD rate limit and more lookups per domain), CERTSPOTTER_API_KEY.
Limitations
- Versions are "detected", never "confirmed". They come from banners, meta tags and asset URLs; vendors backport patches, and many sites hide versions. CVE matches are leads to verify, not proof of vulnerability.
- Only versions exposed on the homepage and its headers are seen; no JavaScript is executed and no other pages are crawled, so client-rendered or login-gated stacks are under-reported.
- Subdomain discovery depends on certificate-transparency logs: hosts that never had a public certificate are invisible. crt.sh is occasionally slow or down (Cert Spotter is the fallback; both unavailable ⇒
unavailable, not charged). Very large domains are truncated tomaxSubdomainsresolved names. - Takeover results are candidates from CNAME/NXDOMAIN and public error-page fingerprints; false positives and negatives are possible.
- DKIM can only be checked on known selectors. MTA-STS/BIMI checks do not validate the BIMI logo or VMC.
- TLS reports the protocol negotiated with this client; it does not enumerate every supported protocol or cipher.
- NVD without an API key is throttled (about 5 requests / 30 s) and capped at 4 product lookups per domain; set
NVD_API_KEYto raise it. Large bulkfull_reportruns are therefore slower. diffcompares against the previous run's snapshot in your account; deleting the store resets the baseline (free again).- Public data only: domains behind login, or sites that block robots, produce partial or
unavailableresults.
Related Actors
- Google Trends Scraper: Bulk Compare 100+ Keywords: compare search interest in a company or brand with its competitors, up to 500 names on one Google Trends scale.
- YouTube Transcripts for AI: bulk YouTube transcripts from videos, channels and search as text, Markdown and RAG chunks.
Feedback
Something not working, or a feature you need? Open an issue in the Issues tab with your input and I'll take a look. If the Actor saves you time, a short review on its Store page helps other people find it.
Local development
npm cinpm run build && npm run lint && npm test # offline unit tests with fixturesnpm run smoke # live: runs every mode against 3 real domains and prints the outputsapify run # default input = example.com demo