# Security Headers Checker & Website Audit: CSP, HSTS, Cookies (`bifras/website-security-privacy-audit`) Actor

Bulk-check public websites for CSP, HSTS, X-Frame-Options, referrer policy, cookie flags, HTTPS/TLS details and security.txt contacts. One report per page with evidence and fixes. Cookie values are never stored. $3 per 1,000 pages, no start fee.

- **URL**: https://apify.com/bifras/website-security-privacy-audit.md
- **Developed by:** [Francesco Michael Billeci](https://apify.com/bifras) (community)
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 page auditeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Website Security & Privacy Audit

Audit the public HTTP configuration of a website without running a browser or collecting page content. Paste website URLs and get **one structured report per unique page** with evidence and practical recommendations.

The audit checks **CSP, HSTS, framing protection, MIME sniffing protection, referrer policy, cookie flags, HTTPS/TLS connection details and security.txt contacts**. Use it to review a website portfolio, prepare a client configuration report, or compare the same pages after a deployment.

### Quick start

1. Select **Start** with the default `https://example.com` input, or add your own public URLs.
2. Open **Overview** for the summary, **Findings** for evidence and recommendations, or **Cookies** for cookie attributes.
3. Export the dataset as JSON, CSV or Excel, or read it through the Apify API.

```json
{
  "urls": [{"url": "https://example.com"}],
  "checkSecurityTxt": true,
  "maxUrls": 100,
  "concurrency": 3
}
```

Domains without a scheme default to HTTPS. Query strings and fragments are removed before requesting a page. The Actor follows up to six redirects. Exact normalized URLs are deduplicated before checking; different page paths are separate audits.

### What you get

| Area | Observations |
|---|---|
| HTTP and redirects | Final URL, status, HTTPS use and redirect chain |
| Security headers | Enforced and report-only CSP, HSTS max-age, X-Frame-Options, nosniff and referrer policy |
| Cookies | Cookie name, Secure, HttpOnly, SameSite, domain and path; **never the cookie value** |
| TLS | Version, cipher and certificate expiry **when connection details are available**; HTTPS certificates are verified during connection |
| security.txt | Published contacts, expiry and canonical links from `/.well-known/security.txt` |
| Findings | Severity, evidence and a recommendation for each observed configuration issue |

**No arbitrary security score.** A missing header is an observation to review in context, not proof that a vulnerability is exploitable. A page with no findings is not guaranteed secure.

Authentication/session cookie purpose is inferred from the cookie name, so review HttpOnly recommendations before applying them. JavaScript-readable cookies can be intentional. Report-only CSP is reported separately from an enforced policy.

### Input

| Field | Default | Purpose |
|---|---:|---|
| `urls` | example.com | Public domains or HTTP(S) page URLs |
| `checkSecurityTxt` | true | Retrieve the final HTTPS origin's public security.txt |
| `maxUrls` | 100 | Maximum unique pages; hard cap 1,000 |
| `concurrency` | 3 | Parallel pages, from 1 to 10 |

Only standard web ports 80 and 443 are supported. Local/private/reserved IPs, credential-bearing URLs and private DNS answers are blocked, including on redirects. Each page audit has a 50-second overall limit and bounded request timeouts.

### Pricing

**$0.003 per successfully audited page: $3.00 per 1,000.** Analysis platform usage is included. There is no start fee. Invalid inputs, failed connections, TLS verification failures and final non-2xx HTTP responses are not charged. A successful page audit is charged even when an optional security.txt file is missing or unavailable.

| Successfully audited pages | Analysis price (USD) |
|---|---:|
| 1 | $0.003 |
| 100 | $0.30 |
| 1,000 | $3.00 |

The Actor honors the run's maximum total charge and stops scheduling more paid work when the limit is reached. Separate Apify charges for keeping or accessing stored results are governed by your plan.

### Use it from Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("bifras/website-security-privacy-audit").call(
    run_input={"urls": [{"url": "https://example.com"}], "maxUrls": 10}
)
for report in client.dataset(run["defaultDatasetId"]).iterate_items():
    if report.get("status") != "ok":
        print("Audit failed:", report.get("sourceUrl"), report.get("error"))
        continue
    print(report["finalUrl"], report["highestSeverity"], report["findingCount"])
    for finding in report.get("findings", []):
        print(finding["severity"], finding["title"], finding["recommendation"])
```

You can also schedule repeated checks, connect webhooks, or call the Actor from Make, Zapier, n8n and AI-agent workflows.

### What this audit does not cover

- It reads response headers and the small public security.txt file. It does **not execute JavaScript**, accept consent banners, inspect browser storage or see cookies set only by scripts.
- Headers are observed on the final response of the requested page. Other routes, geographic locations, CDNs or logged-in users may receive different policies.
- TLS details describe one negotiated connection when available. This is not a scan of all supported TLS protocols or cipher suites, nor an OCSP/revocation or HSTS preload-list check.
- A CSP delivered through an HTML meta element is not inspected. Findings about a missing CSP refer specifically to the response header.
- security.txt discovery does not verify signatures, contact ownership or full RFC compliance. Expired policies are explicitly flagged.
- It performs normal HTTP GETs, without login, exploitation, port scanning or legal-compliance certification.

### Troubleshooting

`status: "error"` means that the requested page could not be audited, for example because DNS failed, TLS validation failed, a redirect was blocked, or the final server response was not successful. These rows are free. Some websites reject data-center requests; try a different public page on that website if appropriate.

`tls.observed: false` means the HTTP client could not expose connection details. It is not evidence of a TLS failure. HTTPS certificate verification still occurs during the request.

`securityTxt.status: "unavailable"` leaves the main page audit intact. A missing security.txt contact is not itself proof of a security vulnerability.

# Actor input Schema

## `urls` (type: `array`):

Public HTTP(S) pages or domains. Only ports 80/443. Query strings and fragments are removed.

## `checkSecurityTxt` (type: `boolean`):

Retrieve the final HTTPS origin's public security.txt. Missing contacts do not fail a page audit.

## `maxUrls` (type: `integer`):

Limit the unique URLs processed by this run.

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

Number of pages checked at once. Lower values reduce load.

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://example.com"
    }
  ],
  "checkSecurityTxt": true,
  "maxUrls": 100,
  "concurrency": 3
}
```

# Actor output Schema

## `reports` (type: `string`):

No description

# 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 = {
    "urls": [
        {
            "url": "https://example.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("bifras/website-security-privacy-audit").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 = { "urls": [{ "url": "https://example.com" }] }

# Run the Actor and wait for it to finish
run = client.actor("bifras/website-security-privacy-audit").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 '{
  "urls": [
    {
      "url": "https://example.com"
    }
  ]
}' |
apify call bifras/website-security-privacy-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bifras/website-security-privacy-audit"
        }
    }
}
```

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/kmdec0wAdXj8Cegdm/builds/ss0gxCUDKDD5sh3Ms/openapi.json
