# Domain Security Checker (`automation-lab/bulk-domain-security-posture-checker`) Actor

Audit public domains across DNS, SPF, optional DKIM, DMARC, TLS, HTTPS, and security headers with severity-tagged remediation findings.

- **URL**: https://apify.com/automation-lab/bulk-domain-security-posture-checker.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $14.40 / 1,000 domain security reports

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Bulk Domain Security Posture Checker

Audit public domains and export one normalized row per domain with DNS, SPF, optional-selector DKIM, DMARC, TLS certificate, HTTPS, and security-header observations.

This **domain security checker** turns live public protocol evidence into severity-tagged findings, a diagnostic score, timestamps, and practical remediation guidance. Use it for one-time reviews or scheduled portfolio checks in Apify.

### What does Bulk Domain Security Posture Checker do?

For every unique hostname, the Actor checks:

- A and AAAA address records
- MX and authoritative NS records
- CAA certificate-authority restrictions
- SPF presence, multiplicity, weak policies, and DNS-lookup pressure
- DMARC presence, policy, percentage, subdomain policy, and reporting destinations
- caller-supplied DKIM selectors
- TLS reachability, trust, protocol, cipher, issuer, subject, and validity dates
- HTTPS status, redirect destination, and response time
- HSTS, CSP, nosniff, clickjacking, referrer, and permissions controls

It returns partial observations when one subsystem fails. A missing DNS record is a typed finding, not a silently dropped domain.

### Who is it for?

Security teams can triage a company domain portfolio.

Managed service providers can produce comparable customer-domain reports.

IT and email administrators can find SPF and DMARC remediation work.

Platform teams can watch certificate expiry and web-header regressions.

Data teams can export normalized rows to a warehouse, spreadsheet, webhook, or reporting workflow.

This Actor is diagnostic. It does not certify compliance and does not perform intrusive vulnerability scanning.

### Why use this Actor?

- **One row per domain:** easy to compare between scheduled runs.
- **Broad posture in one request:** DNS, email authentication, TLS, HTTPS, and headers share one output contract.
- **Explicit DKIM behavior:** selectors are optional; without them DKIM is `not_checked`, never guessed.
- **Remediation-ready findings:** every finding has severity, category, stable code, message, and suggested action.
- **Bounded network behavior:** DNS retries and one HTTPS transient retry are built in; there is no hidden browser or residential-proxy fallback.
- **International domain support:** Unicode hostnames are normalized to ASCII/Punycode.

### What data is returned?

| Field | Meaning |
| --- | --- |
| `input` | Original domain or URL supplied |
| `domain` | Normalized hostname checked |
| `status` | `secure`, `attention`, `high_risk`, or `error` |
| `score` | Diagnostic score from 0 to 100 |
| `highestSeverity` | Highest current finding severity |
| `findingCount` | Number of findings |
| `findings[]` | Severity, category, code, message, and remediation |
| `dns` | A, AAAA, MX, NS, CAA, and resolver errors |
| `spf` | SPF status, optional raw values, and lookup mechanism count |
| `dmarc` | Policy, enforcement state, reporting URIs, and optional raw values |
| `dkim` | Per-selector query observations |
| `tls` | Certificate and negotiated TLS observations |
| `https` | Status, final URL, timing, and security headers |
| `checkedAt` | UTC audit timestamp |
| `durationMs` | Total time spent on the domain |

Output fields are nullable where the public service does not expose a value or a subsystem is unreachable.

### How to run your first audit

1. Open the Actor in Apify Console.
2. Add one or more public domains in **Domains or URLs**.
3. Optionally add DKIM selectors you know are used by those domains.
4. Keep concurrency at 10 for a normal portfolio.
5. Click **Start**.
6. Open the default dataset to inspect or export the normalized rows.
7. Schedule the Task when you want recurring snapshots.

A useful starter input is:

```json
{
  "domains": ["apify.com", "github.com", "wikipedia.org"],
  "concurrency": 3,
  "timeoutMs": 10000
}
```

### Input parameters

#### `domains`

Required list of 1–1,000 public domain names or URLs. URLs are reduced to hostnames and duplicates are checked once. IP literals, single-label names, empty strings, and malformed hostnames fail input validation.

#### `dkimSelectors`

Optional list of up to 20 DNS-safe selectors. Examples include `google`, `selector1`, and `selector2`. DKIM selectors are chosen by each sender and cannot be enumerated reliably through DNS. If this list is empty, output reports `dkim.status` as `not_checked`.

#### `concurrency`

Number of domains processed together, from 1 to 50. The default is 10. Lower it for slow or fragile infrastructure.

#### `timeoutMs`

Per-operation timeout from 1,000 to 30,000 milliseconds. The default is 10,000.

#### `includeRawRecords`

When enabled, raw SPF, DMARC, and matched DKIM values are included. Parsed status and remediation fields are always available.

### Output example

This abbreviated record reflects the current output shape:

```json
{
  "input": "apify.com",
  "domain": "apify.com",
  "status": "attention",
  "score": 90,
  "highestSeverity": "low",
  "findingCount": 2,
  "findings": [
    {
      "severity": "low",
      "category": "headers",
      "code": "missing_permissions_policy",
      "message": "Permissions-Policy is absent.",
      "remediation": "Disable browser capabilities the application does not need."
    }
  ],
  "spf": { "status": "valid", "record": null, "records": [], "lookupMechanismCount": 2 },
  "dmarc": { "status": "enforced", "policy": "reject", "percentage": 100 },
  "dkim": { "status": "not_checked", "selectors": [] },
  "tls": { "reachable": true, "authorized": true, "protocol": "TLSv1.3" },
  "https": { "reachable": true, "statusCode": 200, "finalUrl": "https://apify.com/" },
  "checkedAt": "2025-01-15T12:00:00.000Z",
  "durationMs": 742
}
```

Exact records, certificates, headers, scores, and findings change as domain owners update infrastructure.

### How findings and scores work

Findings are ordered from highest to lowest severity.

Critical findings include an unreachable TLS service, an untrusted certificate, or an expired certificate.

High findings include missing SPF/DMARC, invalid duplicate policies, no nameservers, certificate expiry within 30 days, legacy TLS, or an HTTPS server error.

Medium and low findings identify weaker policies and missing defense-in-depth headers.

The score subtracts fixed weights from 100 and floors at zero. It is a prioritization aid, not a standards audit, penetration test, or guarantee of security.

### How much does it cost to check domain security posture?

Pay-per-event pricing includes a **$0.005 start fee** and one charge per emitted domain report.

| Apify plan tier | Price per domain |
| --- | ---: |
| Free | $0.02760 |
| Bronze | $0.02400 |
| Silver | $0.01872 |
| Gold | $0.01440 |
| Platinum | $0.01440 |
| Diamond | $0.01440 |

At Bronze, 1 domain costs about **$0.029**, 10 domains about **$0.245**, and 100 domains about **$2.405**, including one start event. Platform usage is included in PPE pricing; failed input validation does not create domain rows.

Prices shown here must match the active Actor pricing configuration. Apify may apply plan-specific billing behavior.

### Recurring portfolio monitoring

Create an Apify Task with a stable domain list and schedule it daily, weekly, or monthly. Each run writes fresh `checkedAt`, certificate lifetime, status, score, and finding codes.

For change tracking, compare rows by `domain`. Alert when:

- `highestSeverity` increases
- a new finding code appears
- `tls.daysRemaining` crosses your threshold
- `dmarc.status` leaves `enforced`
- HTTPS becomes unreachable
- a required security header disappears

The Actor stores snapshots; it does not send alerts or compare prior datasets by itself.

### Export and integration workflows

Download the dataset as JSON, CSV, Excel, XML, or RSS from Apify.

Connect runs to:

- Google Sheets for remediation registers
- Slack or email automation for high-severity findings
- a SIEM or warehouse for trend analysis
- Make, Zapier, n8n, or webhooks
- an internal asset inventory keyed by normalized domain

Stable finding codes are better automation keys than human-readable messages.

### Run with the Apify API

Replace `YOUR_TOKEN` with an Apify API token.

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~bulk-domain-security-posture-checker/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains":["apify.com","github.com"],"dkimSelectors":["google"]}'
```

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/bulk-domain-security-posture-checker').call({
  domains: ['apify.com', 'github.com'],
  dkimSelectors: ['google'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("automation-lab/bulk-domain-security-posture-checker").call(
    run_input={"domains": ["apify.com", "github.com"]}
)
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use with Apify MCP

Add this Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/bulk-domain-security-posture-checker"
```

#### Claude Desktop, Cursor, and VS Code setup

For Claude Desktop, Cursor, VS Code, or another MCP-capable desktop/editor client:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/bulk-domain-security-posture-checker"
    }
  }
}
```

Example prompts:

- “Audit apify.com and github.com, then summarize only high and critical findings.”
- “Check these customer domains and return a CSV-ready remediation list.”
- “Run the domain portfolio Task and compare finding codes with last week’s dataset.”

### Reliability, retries, and network vantage point

The Actor runs direct DNS, TLS, and HTTPS checks from its Apify container. That is the observed vantage point; results may differ from a corporate network, another geography, split-horizon DNS, or a browser with authenticated cookies.

The DNS resolver makes two attempts. HTTPS makes one bounded retry for transient network/status failures. Stable malformed inputs are not retried. There is no automatic residential proxy or browser fallback.

Each domain still gets a row when only part of the audit fails. Inspect `dns.errors`, `tls.error`, `https.error`, and related findings.

### Limits and interpretation

- Only public domain infrastructure is supported.
- The Actor does not scan ports other than TLS on 443.
- It does not test application vulnerabilities, cipher suites exhaustively, DNSSEC validation chains, certificate-chain internals, MTA-STS, TLS-RPT, BIMI, or SMTP delivery.
- A DKIM result covers only selectors you supply.
- Wildcard, CDN, geolocation, and load-balancer behavior can vary between runs.
- Security headers on `/` may differ from headers on other routes.
- HSTS should be enabled carefully only after HTTPS coverage is complete.
- Scores are diagnostic and may not match an organization’s risk framework.

### Legality and responsible use

The Actor queries public DNS and makes a normal HTTPS request to each supplied hostname. Use it only for domains you are authorized to assess and at a reasonable frequency.

Do not describe the output as a compliance certificate, penetration test, or proof that a domain is safe. Review remediation against your architecture and change-management process.

Avoid placing credentials, private hostnames, session cookies, or personal data in input. This Actor does not need them.

### Troubleshooting

#### Why is DKIM `not_checked`?

No selectors were supplied. Add the selector names configured by the domain’s mail providers. The Actor intentionally does not guess a fixed universal list.

#### Why is a known domain marked `error`?

Several DNS operations plus TLS and HTTPS were unreachable from the run. Check `dns.errors`, `tls.error`, and `https.error`, increase `timeoutMs` within the supported range, then confirm the hostname is public.

#### Why did two inputs produce one row?

URLs and hostnames are normalized and deduplicated. `https://www.example.org/path` and `www.example.org` refer to the same hostname.

#### Why did the score change?

DNS, certificates, redirects, headers, and upstream availability are live observations. Compare finding codes and raw subsystem fields to explain the change.

### Related Automation Lab Actors

- [Bulk DMARC Record Checker](https://apify.com/automation-lab/bulk-dmarc-record-checker) for deeper DMARC syntax and reporting diagnostics.
- [Bulk DNS Record Lookup](https://apify.com/automation-lab/bulk-dns-lookup) for general A, AAAA, MX, NS, TXT, CNAME, and SOA inventory.
- [Domain Registration & RDAP Lookup](https://apify.com/automation-lab/domain-rdap-lookup) for registrar, expiry, nameserver, and registration status data.
- [Lighthouse Website Audit](https://apify.com/automation-lab/website-lighthouse-seo-audit) for performance, accessibility, best-practice, and SEO checks.

Use this Actor when the primary job is a normalized cross-layer security posture report rather than one specialized protocol lookup.

### FAQ

#### Does the Actor modify a domain?

No. It performs read-only public DNS queries, a TLS handshake, and an HTTPS GET.

#### Can it check subdomains?

Yes. Supply each public subdomain as its own input. Email-authentication policies can inherit differently, so interpret missing records in context.

#### Does a score of 100 prove a domain is secure?

No. It only means this bounded checklist produced no findings. Application security and many infrastructure controls are outside scope.

#### Can I run it on a schedule?

Yes. Save the input as an Apify Task and configure a schedule. Dataset rows contain timestamps suitable for snapshot comparison.

#### Are raw SPF and DMARC values included?

Only when `includeRawRecords` is true. Parsed statuses and findings are always returned.

#### What happens when one domain is offline?

The Actor emits a typed row with errors and remediation findings, then continues with the remaining domains.

# Actor input Schema

## `domains` (type: `array`):

Public domain names or URLs to audit. URLs are normalized to their hostnames; duplicate hostnames are checked once. Maximum 1,000 entries.

## `dkimSelectors` (type: `array`):

Known DKIM selector names to query for every domain, such as google, selector1, or selector2. DKIM cannot be discovered reliably without selectors, so omit this field to report DKIM as not checked.

## `concurrency` (type: `integer`):

Number of domains audited in parallel. Reduce this when checking slow or rate-limited infrastructure.

## `timeoutMs` (type: `integer`):

Per-operation timeout for DNS, TLS, and HTTPS checks. HTTPS transient failures receive one bounded retry.

## `includeRawRecords` (type: `boolean`):

Include raw SPF, DMARC, and matched DKIM TXT values in output. Leave disabled for smaller recurring-audit datasets.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "github.com",
    "wikipedia.org"
  ],
  "dkimSelectors": [],
  "concurrency": 10,
  "timeoutMs": 10000,
  "includeRawRecords": false
}
```

# Actor output Schema

## `overview` (type: `string`):

One normalized DNS, email-authentication, TLS, HTTPS, and security-header posture report per unique domain

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "domains": [
        "apify.com",
        "github.com",
        "wikipedia.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/bulk-domain-security-posture-checker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "domains": [
        "apify.com",
        "github.com",
        "wikipedia.org",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/bulk-domain-security-posture-checker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domains": [
    "apify.com",
    "github.com",
    "wikipedia.org"
  ]
}' |
apify call automation-lab/bulk-domain-security-posture-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/bulk-domain-security-posture-checker"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/wRejqPrgnp9c3QzSD/builds/PeJzA7XaPge9Eyymb/openapi.json
