Skip to content

fedlinllc/security-headers-scanner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Security Headers Scanner

Cloudflare Workers Mozilla Observatory License: MIT

A Cloudflare Worker that analyzes HTTP security headers using Mozilla Observatory methodology. Get instant letter grades (A+ through F) for any website's security posture.

Live Demo: fedlin.com — Try the scanner on the homepage.


Features

  • Mozilla Observatory Scoring — Exact methodology match for consistent grades
  • Comprehensive Analysis — CSP, HSTS, X-Frame-Options, and more
  • Subresource Integrity — Checks external scripts for integrity attributes
  • Redirect Analysis — Validates HTTP → HTTPS redirect behavior
  • Zero Dependencies — Pure Cloudflare Worker, no external packages
  • CORS Enabled — Use from any frontend application

Quick Start

Option 1: Deploy to Cloudflare (Recommended)

# Install Wrangler CLI
npm install -g wrangler

# Clone this repo
git clone https://github.com/fedlinllc/security-headers-scanner.git
cd security-headers-scanner

# Login to Cloudflare
wrangler login

# Deploy
wrangler deploy

Your scanner will be live at: https://security-headers-scanner.<your-subdomain>.workers.dev

Option 2: Cloudflare Dashboard

  1. Go to Cloudflare DashboardWorkers & Pages
  2. Click Create ApplicationCreate Worker
  3. Name it security-headers-scanner
  4. Click Deploy, then Edit Code
  5. Paste the contents of src/index.js
  6. Click Save and Deploy

Usage

API Request

curl "https://your-worker.workers.dev/?url=https://example.com"

Response

{
  "success": true,
  "url": "https://example.com",
  "finalUrl": "https://example.com/",
  "statusCode": 200,
  "score": 85,
  "maxScore": 135,
  "grade": "A-",
  "gradeColor": "green",
  "gradeLabel": "Good",
  "passed": 6,
  "warnings": 1,
  "missing": 2,
  "total": 10,
  "headers": [
    {
      "name": "Content-Security-Policy",
      "key": "content-security-policy",
      "status": "present",
      "value": "default-src 'self'",
      "scoreImpact": "Pass",
      "notes": null
    }
    // ... more headers
  ],
  "rawHeaders": {
    "content-security-policy": "default-src 'self'",
    "strict-transport-security": "max-age=31536000"
    // ... all response headers
  }
}

Scoring

Scoring matches Mozilla Observatory:

Grade Score Range Label
A+ 100+ Excellent
A 90-99 Very Good
A- 85-89 Good
B+ 80-84 Above Average
B 70-79 Average
B- 65-69 Below Average
C+ 60-64 Fair
C 50-59 Needs Work
C- 45-49 Poor
D+ 40-44 Bad
D 35-39 Very Bad
D- 30-34 Critical
F 0-29 Failing

Header Scoring

Header Missing Penalty Notes
Content-Security-Policy -25 XSS protection
Strict-Transport-Security -20 HTTPS enforcement
X-Frame-Options -20 Clickjacking protection
X-Content-Type-Options -5 MIME sniffing prevention
Referrer-Policy 0 +5 bonus for private values

Additional Checks

Check Impact
Subresource Integrity -50 if HTTP scripts, -5 if missing SRI, +5 if all pass
HTTP→HTTPS Redirect -5 to -20 for improper redirects
Cookie Security -20 if missing Secure flag on HTTPS

Headers Checked

Header Purpose
Content-Security-Policy Prevents XSS and data injection
Strict-Transport-Security Forces HTTPS connections
X-Frame-Options Prevents clickjacking
X-Content-Type-Options Prevents MIME sniffing
Referrer-Policy Controls referrer leakage
Permissions-Policy Restricts browser features
Cross-Origin-Opener-Policy Isolates browsing context
Cross-Origin-Resource-Policy Controls cross-origin loading

Frontend Integration

JavaScript Example

async function scanHeaders(url) {
  const response = await fetch(
    `https://your-worker.workers.dev/?url=${encodeURIComponent(url)}`
  );
  const data = await response.json();
  
  console.log(`Grade: ${data.grade} (${data.score}/${data.maxScore})`);
  console.log(`Passed: ${data.passed}, Warnings: ${data.warnings}, Missing: ${data.missing}`);
  
  return data;
}

// Usage
scanHeaders('https://example.com').then(console.log);

React Example

import { useState } from 'react';

function SecurityScanner() {
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(false);
  
  const scan = async (url) => {
    setLoading(true);
    const res = await fetch(`https://your-worker.workers.dev/?url=${encodeURIComponent(url)}`);
    setResult(await res.json());
    setLoading(false);
  };
  
  return (
    <div>
      <input type="url" id="url" placeholder="https://example.com" />
      <button onClick={() => scan(document.getElementById('url').value)}>
        {loading ? 'Scanning...' : 'Scan'}
      </button>
      {result && (
        <div>
          <h2>Grade: {result.grade}</h2>
          <p>Score: {result.score}/{result.maxScore}</p>
        </div>
      )}
    </div>
  );
}

Custom Domain

Add a custom route like api.yourdomain.com/scan:

  1. In Cloudflare Dashboard → Workers → Your Worker → Triggers
  2. Add a Custom Domain or Route
  3. Update your frontend to use the new URL

Development

# Run locally
wrangler dev

# Test
curl "http://localhost:8787/?url=https://cloudflare.com"

Contributing

  1. Fork the repo
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

Related


License

MIT License — see LICENSE


Author

Jeremiah Coakley
Principal Security Architect, FEDLIN LLC

GitHub LinkedIn Website

About

Cloudflare Worker for Mozilla Observatory-aligned security header analysis

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published