Claude Skill

cors-auditor

Audit a site's Cross-Origin Resource Sharing (CORS) configuration for misconfigurations — wildcard origin with credentials, reflected arbitrary Origin, the 'null' origin, overly broad allowed methods, and risky credentialed CORS. Use when the user asks to "check my CORS config",

LLM Mart · 0 points · 0 views 31 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download novacode37-claude-security-skills-skills_cors-auditor-dd4e44f.zip · 4 KB
Part of novacode37/claude-security-skills — 5 skills

Install

skills CLI npx skills add https://github.com/NovaCode37/claude-security-skills/tree/main/skills/cors-auditor
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install novacode37-claude-security-skills@llmmart
Git git clone https://github.com/NovaCode37/claude-security-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole novacode37/claude-security-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

CORS Misconfiguration Auditor

Inspects CORS response headers and reports exploitable misconfigurations with fixes. The analysis core is pure and offline-testable; live probing uses only stdlib urllib and sends a throwaway Origin header to detect origin reflection.

When to use this skill

  • "Audit the CORS configuration of https://api.example.com."
  • "Is it safe that my API returns Access-Control-Allow-Origin: *?"
  • "Does my server reflect any Origin back?"

What it checks

  • Wildcard + credentials — Allow-Origin: * with Allow-Credentials: true (cors-wildcard-credentials).
  • Reflected origin — server echoes the request Origin back (cors-reflected-origin); critical when combined with credentials.
  • Null origin — Allow-Origin: null (cors-null-origin).
  • Wildcard origin — Allow-Origin: * without credentials (cors-wildcard).
  • Credentialed CORS — informational note when credentials are enabled (cors-credentials-enabled).
  • Wildcard methods — Allow-Methods: * (cors-methods-wildcard).

How to run it

# Live probe (sends a throwaway Origin to test reflection)
python skills/cors-auditor/auditor.py https://api.example.com

# Probe with a specific origin
python skills/cors-auditor/auditor.py https://api.example.com --origin https://evil.example

# Offline: audit a captured header block; pass --origin to test reflection
python skills/cors-auditor/auditor.py --headers-file resp.txt --origin https://evil.example

# Only fail CI on high/critical (hides the medium wildcard + info notes)
python skills/cors-auditor/auditor.py https://api.example.com --min-severity high

Exit codes: 0 clean · 1 findings reported · 2 fetch/usage error. Every reported finding fails the build. The default reports everything down to info (including cors-credentials-enabled); raise --min-severity to low/medium/high to filter advisory notes out of both the report and the exit code.

Recommended workflow for Claude

  1. Probe the endpoint (live) or audit captured headers (offline).
  2. Explain each finding and why it is exploitable (e.g. reflected origin + credentials = cross-origin data theft).
  3. Recommend an explicit origin allowlist and dropping credentials where not needed.
  4. Only test APIs the user owns or is authorized to test.
Files (claude-security-skills)
  • tests
    • test_auditor.py 5.3 KB
      import os
      import sys
      
      sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
      
      import auditor
      
      
      def _ids(headers, sent_origin=None):
          return {f.id for f in auditor.audit_cors(headers, sent_origin)}
      
      
      def test_no_cors_headers_clean():
          assert auditor.audit_cors({"Content-Type": "text/html"}) == []
      
      
      def test_wildcard_with_credentials_critical():
          headers = {
              "Access-Control-Allow-Origin": "*",
              "Access-Control-Allow-Credentials": "true",
          }
          findings = auditor.audit_cors(headers)
          assert any(f.id == "cors-wildcard-credentials"
                     and f.severity == "critical" for f in findings)
      
      
      def test_wildcard_without_credentials_medium():
          findings = auditor.audit_cors({"Access-Control-Allow-Origin": "*"})
          assert any(f.id == "cors-wildcard" and f.severity == "medium"
                     for f in findings)
      
      
      def test_null_origin_flagged():
          assert "cors-null-origin" in _ids({"Access-Control-Allow-Origin": "null"})
      
      
      def test_null_origin_with_credentials_critical():
          headers = {
              "Access-Control-Allow-Origin": "null",
              "Access-Control-Allow-Credentials": "true",
          }
          findings = auditor.audit_cors(headers)
          assert any(f.id == "cors-null-origin" and f.severity == "critical"
                     for f in findings)
      
      
      def test_reflected_origin_detected():
          origin = "https://evil.example"
          headers = {"Access-Control-Allow-Origin": origin}
          assert "cors-reflected-origin" in _ids(headers, sent_origin=origin)
      
      
      def test_reflected_origin_with_credentials_critical():
          origin = "https://evil.example"
          headers = {
              "Access-Control-Allow-Origin": origin,
              "Access-Control-Allow-Credentials": "true",
          }
          findings = auditor.audit_cors(headers, sent_origin=origin)
          assert any(f.id == "cors-reflected-origin" and f.severity == "critical"
                     for f in findings)
      
      
      def test_fixed_origin_no_reflection():
          headers = {"Access-Control-Allow-Origin": "https://app.example.com"}
          assert "cors-reflected-origin" not in _ids(
              headers, sent_origin="https://evil.example")
      
      
      def test_credentialed_fixed_origin_info():
          headers = {
              "Access-Control-Allow-Origin": "https://app.example.com",
              "Access-Control-Allow-Credentials": "true",
          }
          assert "cors-credentials-enabled" in _ids(headers)
      
      
      def test_methods_wildcard_flagged():
          headers = {
              "Access-Control-Allow-Origin": "https://app.example.com",
              "Access-Control-Allow-Methods": "*",
          }
          assert "cors-methods-wildcard" in _ids(headers)
      
      
      def test_parse_raw_headers():
          text = (
              "HTTP/1.1 200 OK\n"
              "Access-Control-Allow-Origin: *\n"
              "Content-Type: application/json\n"
          )
          headers = auditor.parse_raw_headers(text)
          assert headers["Access-Control-Allow-Origin"] == "*"
      
      
      def test_cli_exit_code_high(tmp_path):
          f = tmp_path / "resp.txt"
          f.write_text("Access-Control-Allow-Origin: *\n"
                       "Access-Control-Allow-Credentials: true\n")
          assert auditor.main(["--headers-file", str(f)]) == 1
      
      
      def test_cli_exit_code_clean(tmp_path):
          f = tmp_path / "resp.txt"
          f.write_text("Access-Control-Allow-Origin: https://app.example.com\n")
          assert auditor.main(["--headers-file", str(f)]) == 0
      
      
      def test_cli_json(tmp_path, capsys):
          import json
          f = tmp_path / "resp.txt"
          f.write_text("Access-Control-Allow-Origin: null\n")
          auditor.main(["--headers-file", str(f), "--json"])
          data = json.loads(capsys.readouterr().out)
          assert isinstance(data, list) and data[0]["id"] == "cors-null-origin"
      
      
      def test_cli_medium_finding_exits_one(tmp_path):
          """A reported finding fails the build regardless of severity (issue #46)."""
          f = tmp_path / "resp.txt"
          f.write_text("Access-Control-Allow-Origin: *\n")
          assert auditor.main(["--headers-file", str(f)]) == 1
      
      
      def test_cli_min_severity_filters_report_and_exit(tmp_path, capsys):
          import json
          f = tmp_path / "resp.txt"
          f.write_text("Access-Control-Allow-Origin: *\n")
          rc = auditor.main(["--headers-file", str(f), "--min-severity", "high",
                             "--json"])
          assert json.loads(capsys.readouterr().out) == []
          assert rc == 0
      
      
      def test_filter_by_severity():
          findings = auditor.audit_cors({"Access-Control-Allow-Origin": "*"})
          assert auditor.filter_by_severity(findings, "medium") == findings
          assert auditor.filter_by_severity(findings, "high") == []
      
      
      INFO_ONLY_HEADERS = ("Access-Control-Allow-Origin: https://app.example.com\n"
                           "Access-Control-Allow-Credentials: true\n")
      
      
      def test_cli_info_finding_reported_by_default(tmp_path, capsys):
          """INFO findings stay in the default report and still fail the build."""
          import json
          f = tmp_path / "resp.txt"
          f.write_text(INFO_ONLY_HEADERS)
          rc = auditor.main(["--headers-file", str(f), "--json"])
          data = json.loads(capsys.readouterr().out)
          assert [d["id"] for d in data] == ["cors-credentials-enabled"]
          assert [d["severity"] for d in data] == ["info"]
          assert rc == 1
      
      
      def test_cli_min_severity_low_hides_info(tmp_path, capsys):
          import json
          f = tmp_path / "resp.txt"
          f.write_text(INFO_ONLY_HEADERS)
          rc = auditor.main(["--headers-file", str(f), "--min-severity", "low",
                             "--json"])
          assert json.loads(capsys.readouterr().out) == []
          assert rc == 0
      
  • auditor.py 6.1 KB
    from __future__ import annotations
    
    import argparse
    import json
    import sys
    from dataclasses import dataclass, asdict
    
    SEV_RANK = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
    
    PROBE_ORIGIN = "https://cors-probe.invalid"
    
    @dataclass
    class Finding:
        id: str
        severity: str
        message: str
        recommendation: str
    
        def to_dict(self) -> dict:
            return asdict(self)
    
    def _lower(headers: dict) -> dict:
        return {str(k).lower(): str(v) for k, v in headers.items()}
    
    def audit_cors(headers: dict, sent_origin: str | None = None) -> list:
        h = _lower(headers)
        out: list = []
        if "access-control-allow-origin" not in h:
            return out
        acao = h["access-control-allow-origin"].strip()
        acac = h.get("access-control-allow-credentials", "").strip().lower() == "true"
        acam = h.get("access-control-allow-methods", "")
    
        if acao == "*":
            if acac:
                out.append(Finding(
                    "cors-wildcard-credentials", "critical",
                    "Access-Control-Allow-Origin is '*' together with "
                    "Allow-Credentials: true.",
                    "Never combine a wildcard origin with credentials; echo a "
                    "vetted origin from an allowlist instead."))
            else:
                out.append(Finding(
                    "cors-wildcard", "medium",
                    "Access-Control-Allow-Origin is '*' — any site can read "
                    "responses.",
                    "Restrict to an explicit allowlist of trusted origins."))
        elif acao.lower() == "null":
            out.append(Finding(
                "cors-null-origin", "critical" if acac else "high",
                "Access-Control-Allow-Origin allows 'null', reachable from sandboxed "
                "iframes and local files.",
                "Never allow the 'null' origin; use explicit https origins."))
        elif sent_origin and acao == sent_origin:
            out.append(Finding(
                "cors-reflected-origin", "critical" if acac else "high",
                f"The server reflects an arbitrary Origin ({sent_origin}) back in "
                "Access-Control-Allow-Origin.",
                "Validate Origin against an allowlist; do not echo it blindly."))
    
        if acac and acao == "*":
            pass
        elif acac and acao not in ("", "*") and acao.lower() != "null" \
                and not (sent_origin and acao == sent_origin):
            out.append(Finding(
                "cors-credentials-enabled", "info",
                f"Credentialed CORS is enabled for origin '{acao}'.",
                "Confirm this origin is fully trusted; credentials expose "
                "authenticated data."))
    
        if "*" in [m.strip() for m in acam.split(",")]:
            out.append(Finding(
                "cors-methods-wildcard", "low",
                "Access-Control-Allow-Methods is '*'.",
                "List only the methods the endpoint actually needs."))
    
        out.sort(key=lambda f: SEV_RANK.get(f.severity, 9))
        return out
    
    def fetch_cors(url: str, origin: str = PROBE_ORIGIN, timeout: float = 10.0):
        import urllib.request
    
        req = urllib.request.Request(
            url, method="GET",
            headers={"Origin": origin, "User-Agent": "cors-auditor/1.0"})
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            return dict(resp.headers.items())
    
    def parse_raw_headers(text: str) -> dict:
        headers: dict = {}
        for line in text.splitlines():
            if not line.strip() or line.startswith("HTTP/") or ":" not in line:
                continue
            key, val = line.split(":", 1)
            headers[key.strip()] = val.strip()
        return headers
    
    def filter_by_severity(findings: list, min_severity: str = "info") -> list:
        threshold = SEV_RANK.get(min_severity, SEV_RANK["info"])
        return [f for f in findings if SEV_RANK.get(f.severity, 3) <= threshold]
    
    def render(target: str, findings: list) -> str:
        if not findings:
            return f"[cors-auditor] {target}: no CORS misconfigurations found. [OK]"
        out = [f"[cors-auditor] {target}: {len(findings)} finding(s):\n"]
        for f in findings:
            out.append(f"  [{f.severity.upper():<8}] {f.id}")
            out.append(f"             {f.message}")
            out.append(f"             fix: {f.recommendation}")
        return "\n".join(out)
    
    def main(argv: list | None = None) -> int:
        p = argparse.ArgumentParser(
            prog="cors-auditor",
            description="Audit a site's CORS configuration for misconfigurations.")
        p.add_argument("url", nargs="?", help="URL to probe (https://...)")
        p.add_argument("--origin", default=PROBE_ORIGIN,
                       help="Origin header to send when probing reflection")
        p.add_argument("--headers-file",
                       help="offline: file with a raw HTTP response header block")
        p.add_argument("--json", action="store_true", help="emit JSON")
        p.add_argument("--min-severity", default="info", choices=list(SEV_RANK),
                       help="report findings at or above this severity "
                            "(default: info, i.e. report everything)")
        args = p.parse_args(argv)
    
        if args.headers_file:
            try:
                with open(args.headers_file, "r", encoding="utf-8",
                          errors="ignore") as fh:
                    headers = parse_raw_headers(fh.read())
            except OSError as exc:
                print(f"error: {exc}", file=sys.stderr)
                return 2
            target = args.headers_file
            sent_origin = args.origin if args.origin != PROBE_ORIGIN else None
        elif args.url:
            try:
                headers = fetch_cors(args.url, args.origin)
            except Exception as exc:
                print(f"error: could not fetch {args.url}: {exc}", file=sys.stderr)
                return 2
            target = args.url
            sent_origin = args.origin
        else:
            p.error("provide a URL or --headers-file")
            return 2
    
        findings = filter_by_severity(
            audit_cors(headers, sent_origin=sent_origin), args.min_severity)
        if args.json:
            print(json.dumps([f.to_dict() for f in findings], indent=2))
        else:
            print(render(target, findings))
        return 1 if findings else 0
    
    if __name__ == "__main__":
        try:
            sys.stdout.reconfigure(encoding="utf-8")
        except Exception:
            pass
        raise SystemExit(main())
    
  • SKILL.md 2.7 KB
    ---
    name: cors-auditor
    description: >-
      Audit a site's Cross-Origin Resource Sharing (CORS) configuration for
      misconfigurations — wildcard origin with credentials, reflected arbitrary
      Origin, the 'null' origin, overly broad allowed methods, and risky
      credentialed CORS. Use when the user asks to "check my CORS config", "is my
      API's CORS safe", "test for CORS misconfiguration", or "why can any site call
      my API".
    license: MIT
    ---
    
    # CORS Misconfiguration Auditor
    
    Inspects CORS response headers and reports exploitable misconfigurations with
    fixes. The analysis core is pure and offline-testable; live probing uses only
    stdlib `urllib` and sends a throwaway `Origin` header to detect origin
    reflection.
    
    ## When to use this skill
    
    - "Audit the CORS configuration of https://api.example.com."
    - "Is it safe that my API returns Access-Control-Allow-Origin: *?"
    - "Does my server reflect any Origin back?"
    
    ## What it checks
    
    - **Wildcard + credentials** — `Allow-Origin: *` with
      `Allow-Credentials: true` (`cors-wildcard-credentials`).
    - **Reflected origin** — server echoes the request Origin back
      (`cors-reflected-origin`); critical when combined with credentials.
    - **Null origin** — `Allow-Origin: null` (`cors-null-origin`).
    - **Wildcard origin** — `Allow-Origin: *` without credentials (`cors-wildcard`).
    - **Credentialed CORS** — informational note when credentials are enabled
      (`cors-credentials-enabled`).
    - **Wildcard methods** — `Allow-Methods: *` (`cors-methods-wildcard`).
    
    ## How to run it
    
    ```bash
    # Live probe (sends a throwaway Origin to test reflection)
    python skills/cors-auditor/auditor.py https://api.example.com
    
    # Probe with a specific origin
    python skills/cors-auditor/auditor.py https://api.example.com --origin https://evil.example
    
    # Offline: audit a captured header block; pass --origin to test reflection
    python skills/cors-auditor/auditor.py --headers-file resp.txt --origin https://evil.example
    
    # Only fail CI on high/critical (hides the medium wildcard + info notes)
    python skills/cors-auditor/auditor.py https://api.example.com --min-severity high
    ```
    
    **Exit codes:** `0` clean · `1` findings reported · `2` fetch/usage error.
    Every reported finding fails the build. The default reports everything down to
    `info` (including `cors-credentials-enabled`); raise `--min-severity` to
    `low`/`medium`/`high` to filter advisory notes out of both the report and the
    exit code.
    
    ## Recommended workflow for Claude
    
    1. Probe the endpoint (live) or audit captured headers (offline).
    2. Explain each finding and why it is exploitable (e.g. reflected origin +
       credentials = cross-origin data theft).
    3. Recommend an explicit origin allowlist and dropping credentials where not
       needed.
    4. Only test APIs the user owns or is authorized to test.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related