Phy Security Headers

SkillDB 作者 PHY041 v1.0.0

HTTP security header auditor that fetches response headers from any URL and grades them against OWASP, Mozilla Observatory, and Google standards. Checks Content-Security-Policy (detects unsafe-inline, wildcard sources, missing directives), HSTS (max-age, includeSubDomains, preload), X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, CORP/COEP/COOP isolation headers. Assigns A/B/C/F grade per header, generates a one-command nginx/Apache/Next.js fix for every gap. Works against any live URL or local dev server via curl. Zero external cloud account. Triggers on "security headers", "CSP audit", "HSTS missing", "clickjacking", "content security policy", "mozilla observatory", "/sec-headers".

源码 ↗

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install skilldb:phy041~phy-security-headers
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/skilldb%3Aphy041~phy-security-headers/file -o phy-security-headers.md
Git 仓库获取源码
git clone https://github.com/openclaw/skills/commit/baa7cb720362dd7dd81b25d0001b4890e2ffa688
# Security Headers Auditor

Your site passes all the security scanners until someone iframes it, injects a script through an open CDN source in your CSP, or steals credentials from a page with no HSTS preload.

This skill fetches your response headers, grades each security header against OWASP and Mozilla Observatory standards, and gives you the exact config line to add to nginx, Apache, Next.js, or Cloudflare Workers.

**Works against any URL via curl. Zero external API.**

---

## Trigger Phrases

- "security headers", "check my headers", "http headers audit"
- "CSP audit", "content security policy"
- "HSTS missing", "HSTS preload"
- "clickjacking protection", "X-Frame-Options"
- "mozilla observatory", "OWASP headers"
- "Permissions-Policy", "CORP COEP COOP"
- "/sec-headers"

---

## How to Provide Input

```bash
# Option 1: Audit a live URL
/sec-headers https://myapp.com

# Option 2: Audit local dev server
/sec-headers http://localhost:3000

# Option 3: Audit specific path
/sec-headers https://myapp.com/dashboard

# Option 4: Audit with custom port
/sec-headers https://staging.myapp.com:8443

# Option 5: Grade only (no fix suggestions)
/sec-headers https://myapp.com --grade-only

# Option 6: Generate nginx config snippet
/sec-headers https://myapp.com --output nginx

# Option 7: Generate Next.js headers config
/sec-headers https://myapp.com --output nextjs
```

---

## Step 1: Fetch Headers

```bash
# Fetch all response headers
URL="https://myapp.com"

curl -sI "$URL" \
  --max-time 10 \
  --user-agent "SecurityHeaderAuditor/1.0" \
  -L  # follow redirects

# Also check www redirect behavior
curl -sI "http://$URL" 2>/dev/null | head -5

# Save raw headers for parsing
HEADERS=$(curl -sI "$URL" --max-time 10 -L 2>/dev/null)
echo "$HEADERS"
```

---

## Step 2: Grade Each Header

```python
import re
import subprocess
import sys
from dataclasses import dataclass, field
from typing import Optional


@dataclass
class HeaderCheck:
    name: str
    present: bool
    value: Optional[str]
    grade: str       # A / B / C / F / MISSING
    issues: list[str]
    fixes: list[str]
    severity: str    # CRITICAL / HIGH / MEDIUM / LOW / INFO


def fetch_headers(url: str) -> dict[str, str]:
    """Fetch HTTP response headers from URL."""
    result = subprocess.run(
        ['curl', '-sI', url, '--max-time', '10', '-L',
         '--user-agent', 'SecurityHeaderAuditor/1.0'],
        capture_output=True, text=True
    )
    headers = {}
    for line in result.stdout.splitlines():
        if ':' in line and not line.startswith('HTTP/'):
            key, _, value = line.partition(':')
            headers[key.strip().lower()] = value.strip()
    return headers


# ── Content-Security-Policy ──────────────────────────────────────────────────

def check_csp(value: Optional[str]) -> HeaderCheck:
    issues = []
    fixes = []
    grade = 'A'

    if not value:
        return HeaderCheck(
            name='Content-Security-Policy',
            present=False, value=None, grade='F',
            issues=['CSP header is missing — XSS attacks have no browser-level mitigation'],
            fixes=[
                "Add to nginx: add_header Content-Security-Policy "
                "\"default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';\" always;",
                "Next.js (next.config.js headers): { key: 'Content-Security-Policy', value: \"default-src 'self'...\" }",
            ],
            severity='CRITICAL',
        )

    directives = {d.split()[0].lower(): d for d in value.split(';') if d.strip()}

    # unsafe-inline in script-src — CRITICAL
    script_src = directives.get('script-src', directives.get('default-src', ''))
    if "'unsafe-inline'" in script_src:
        grade = 'F'
        issues.append("'unsafe-inline' in script-src — negates XSS protection entirely")
        fixes.append("Replace 'unsafe-inline' with a nonce: script-src 'nonce-{random}' 'strict-dynamic'")

    # unsafe-eval
    if "'unsafe-eval'" in script_src:
        grade = 'C' if grade == 'A' else grade
        issues.append("'unsafe-eval' allows eval() — enables second-order XSS")
        fixes.append("Remove 'unsafe-eval'; refactor code using eval() to use Function() alternatives")

    # Wildcard source
    for directive_name, directive_val in directives.items():
        if re.search(r'\bhttps?://\*\b|^\*$', directive_val):
            grade = 'C' if grade == 'A' else grade
            issues.append(f"Wildcard source (*) in {directive_name} — allows loading from any domain")
            fixes.append(f"Replace * in {directive_name} with explicit trusted domains")

    # Missing object-src
    if 'object-src' not in directives and 'default-src' not in directives:
        grade = 'B' if grade == 'A' else grade
        issues.append("Missing object-src — allows Flash/plugin injection if default-src not set")
        fixes.append("Add: object-src 'none'")

    # Missing base-uri
    if 'base-uri' not in directives:
        issues.append("Missing base-uri — allows <base> tag injection to hijack relative URLs")
        fixes.append("Add: base-uri 'self'")

    # report-uri / report-to
    if 'report-uri' not in directives and 'report-to' not in directives:
        issues.append("No CSP violation reporting configured — violations are silent")
        fixes.append("Add: report-to /csp-violations  (or use report-uri https://yoursite.com/csp-report)")

    return HeaderCheck(
        name='Content-Security-Policy', present=True, value=value,
        grade=grade, issues=issues, fixes=fixes, severity='CRITICAL' if grade == 'F' else 'HIGH'
    )


# ── HSTS ─────────────────────────────────────────────────────────────────────

def check_hsts(value: Optional[str], is_https: bool = True) -> HeaderCheck:
    if not value:
        return HeaderCheck(
            name='Strict-Transport-Security', present=False, value=None, grade='F',
            issues=['HSTS missing — browser allows HTTP downgrade attacks'],
            fixes=[
                "nginx: add_header Strict-Transport-Security \"max-age=31536000; includeSubDomains; preload\" always;",
                "Apache: Header always set Strict-Transport-Security \"max-age=31536000; includeSubDomains; preload\"",
            ],
            severity='HIGH',
        )

    issues = []
    fixes = []
    grade = 'A'

    # max-age
    max_age_match = re.search(r'max-age=(\d+)', value, re.I)
    if max_age_match:
        max_age = int(max_age_match.group(1))
        if max_age < 2592000:  # 30 days
            grade = 'C'
            issues.append(f"max-age={max_age} is too short (< 30 days) — not eligible for preload list")
            fixes.append("Set max-age=31536000 (1 year) minimum for preload eligibility")
        elif max_age < 31536000:
            grade = 'B'
            issues.append(f"max-age={max_age} — recommend 31536000 (1 year) for preload eligibility")
    else:
        grade = 'F'
        issues.append("max-age directive missing from HSTS header")
        fixes.append("Add max-age=31536000 to HSTS header")

    # includeSubDomains
    if 'includesubdomains' not in value.lower():
        grade = 'B' if grade == 'A' else grade
        issues.append("Missing includeSubDomains — subdomains can be downgraded to HTTP")
        fixes.append("Add includeSubDomains directive")

    # preload
    if 'preload' not in value.lower():
        issues.append("Missing preload directive — site not eligible for HSTS preload list")
        fixes.append("Add preload directive and submit to hstspreload.org")

    return HeaderCheck(
        name='Strict-Transport-Security', present=True, value=value,
        grade=grade, issues=issues, fixes=fixes, severity='HIGH' if grade != 'A' else 'INFO'
    )


# ── X-Frame-Options ──────────────────────────────────────────────────────────

def check_xfo(value: Optional[str], csp_value: Optional[str] = None) -> HeaderCheck:
    # If CSP has frame-ancestors, XFO is redundant (CSP takes precedence)
    if csp_v