SKILL.md - cors-headers

## What this skill covers

7 stars

Best use case

SKILL.md - cors-headers is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

## What this skill covers

Teams using SKILL.md - cors-headers should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/cors-headers/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/security-privacy/cors-tester/skills/cors-headers/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/cors-headers/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How SKILL.md - cors-headers Compares

Feature / AgentSKILL.md - cors-headersStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

## What this skill covers

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# SKILL.md - cors-headers

## What this skill covers

Complete reference for the HTTP headers involved in the CORS protocol, their semantics, how cors-tester reads them, and the analysis logic implemented in `src/tester.ts`.

## CORS request flow

```
Browser / cors-tester                      Server
       |                                     |
       |  OPTIONS /resource HTTP/1.1         |
       |  Origin: https://app.com            |
       |  Access-Control-Request-Method: POST|
       |  Access-Control-Request-Headers: ... |
       |------------------------------------>|
       |                                     |
       |  HTTP/1.1 204 No Content            |
       |  Access-Control-Allow-Origin: ...   |
       |  Access-Control-Allow-Methods: ...  |
       |  Access-Control-Allow-Headers: ...  |
       |  Access-Control-Max-Age: ...        |
       |<------------------------------------|
       |                                     |
       |  POST /resource HTTP/1.1            |
       |  Origin: https://app.com            |
       |<------------------------------------|
       |  HTTP/1.1 200 OK                    |
       |  Access-Control-Allow-Origin: ...   |
       |  Vary: Origin, Accept-Encoding      |
       |<------------------------------------|
```

## Request headers (sent by cors-tester)

### Origin

Sent on every cross-origin request (preflight and actual).

```
Origin: https://app.example.com
```

- Value is `scheme://host[:port]`. No path, query string, or fragment.
- cors-tester always sends this header with the value provided via `--origin`.

### Access-Control-Request-Method

Sent on the OPTIONS preflight to declare the method the actual request will use.

```
Access-Control-Request-Method: POST
```

- cors-tester sets this to the value of `--method` (default: GET).

### Access-Control-Request-Headers

Sent on the OPTIONS preflight to declare headers the actual request will send.

```
Access-Control-Request-Headers: Authorization, Content-Type, X-Request-ID
```

- cors-tester sets this from `--headers` (comma-separated).
- Omitted entirely if no `--headers` flag is provided.

## Response headers (checked by cors-tester)

### Access-Control-Allow-Origin

The most important CORS header. Must be present and match the requesting origin.

```
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Origin: *
```

| Value | Meaning | cors-tester status |
|-------|---------|-------------------|
| Specific origin matching request | Allowed for this origin | `allowed` |
| `*` (wildcard) | Any origin allowed | `allowed` with `WILDCARD_ORIGIN` warning |
| Specific origin not matching request | Browsers block the response | `denied` |
| Missing | CORS not configured | `denied` with `NO_CORS_HEADERS` error |

cors-tester checks this on both the preflight response and the actual response.

### Access-Control-Allow-Methods

Lists the HTTP methods allowed for cross-origin requests.

```
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
```

- Only meaningful in the preflight response.
- cors-tester checks that the method from `--method` appears in this list (case-insensitive).
- If `Access-Control-Allow-Origin: *` is present, cors-tester does not check this separately; simple requests do not require a preflight.

### Access-Control-Allow-Headers

Lists the request headers allowed in cross-origin requests.

```
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-ID
```

- Only meaningful in the preflight response.
- cors-tester checks that all headers from `--headers` appear in this list (case-insensitive).

### Access-Control-Max-Age

How long (in seconds) the browser may cache the preflight response.

```
Access-Control-Max-Age: 3600
```

Browser cache caps:
- Chrome: 600 seconds (10 minutes)
- Firefox: 7200 seconds (2 hours)
- Safari: 600 seconds

cors-tester emits `LONG_MAX_AGE` warning if the value exceeds 7200.

### Access-Control-Allow-Credentials

Whether the browser may expose the response to JavaScript when credentials (cookies, HTTP auth, TLS client certs) are included.

```
Access-Control-Allow-Credentials: true
```

- When `true`, `Access-Control-Allow-Origin` MUST be a specific origin, not `*`.
- cors-tester emits `WILDCARD_WITH_CREDENTIALS` error when both `*` and `true` are present.
- cors-tester emits `MISSING_CREDENTIALS_HEADER` warning when `--credentials` was passed but this header is absent.

### Access-Control-Expose-Headers

Lists response headers that JavaScript may access. By default, only the CORS-safelisted response headers are accessible.

```
Access-Control-Expose-Headers: X-Request-ID, X-RateLimit-Remaining
```

- cors-tester records this header but does not generate errors or warnings from it.

### Vary

Tells caches which request headers affect the response. Must include `Origin` when the server reflects different `Access-Control-Allow-Origin` values per origin.

```
Vary: Origin, Accept-Encoding
```

cors-tester logic for `MISSING_VARY_ORIGIN`:

```typescript
function checkVaryOrigin(headers: Record<string, string>, allowOrigin: string): Warning | null {
  // Only relevant when the server reflects a specific origin (not wildcard)
  if (allowOrigin === '*') return null;

  const vary = headers['vary'] ?? '';
  const parts = vary.split(',').map(p => p.trim().toLowerCase());

  if (!parts.includes('origin')) {
    return {
      code: 'MISSING_VARY_ORIGIN',
      message: 'Server reflects specific origin but Vary header does not include "Origin". ' +
               'Caches may serve the wrong CORS response to other origins.'
    };
  }
  return null;
}
```

## Analysis logic in tester.ts

### testOrigin() flow

```typescript
async function testOrigin(url: string, origin: string, options: TestOptions): Promise<CorsResult> {
  // 1. Send OPTIONS preflight
  const preflight = await request({
    url,
    method: 'OPTIONS',
    headers: {
      'Origin': origin,
      'Access-Control-Request-Method': options.method ?? 'GET',
      ...(options.requestHeaders?.length
        ? { 'Access-Control-Request-Headers': options.requestHeaders.join(', ') }
        : {}),
    },
    timeout: options.timeout ?? 5000,
  });

  // 2. Check preflight for fatal errors
  const errors: CorsError[] = [];
  const warnings: Warning[] = [];

  const allowOrigin = preflight.headers['access-control-allow-origin'];

  if (!allowOrigin) {
    errors.push({ code: 'NO_CORS_HEADERS', message: '...' });
    return { origin, status: 'denied', errors, warnings, ... };
  }

  // 3. Check for misconfigurations
  const allowCredentials = preflight.headers['access-control-allow-credentials'];
  if (allowOrigin === '*' && allowCredentials === 'true') {
    errors.push({ code: 'WILDCARD_WITH_CREDENTIALS', message: '...' });
  } else if (allowOrigin === '*') {
    warnings.push({ code: 'WILDCARD_ORIGIN', message: '...' });
  }

  // 4. Check Vary on preflight
  const varyWarn = checkVaryOrigin(preflight.headers, allowOrigin);
  if (varyWarn) warnings.push(varyWarn);

  // 5. Check max-age
  const maxAge = parseInt(preflight.headers['access-control-max-age'] ?? '0', 10);
  if (maxAge > 7200) {
    warnings.push({ code: 'LONG_MAX_AGE', message: `Value ${maxAge} exceeds browser cache limits.` });
  }

  // 6. Check credentials header when credentials flag is set
  if (options.credentials && allowCredentials !== 'true') {
    warnings.push({ code: 'MISSING_CREDENTIALS_HEADER', message: '...' });
  }

  // 7. Send actual request
  const actual = await request({
    url,
    method: options.method ?? 'GET',
    headers: { 'Origin': origin },
    timeout: options.timeout ?? 5000,
  });

  // 8. Check Vary on actual response too
  const actualVaryWarn = checkVaryOrigin(actual.headers, allowOrigin);
  if (actualVaryWarn && !varyWarn) warnings.push(actualVaryWarn);

  // 9. Determine final status
  const status = errors.length > 0 ? 'misconfigured'
    : warnings.length > 0 ? 'allowed'   // status still "allowed" but has warnings
    : 'allowed';

  return {
    origin,
    status,
    preflightStatus: preflight.status,
    actualStatus: actual.status,
    durationMs: preflight.durationMs + actual.durationMs,
    corsHeaders: extractCorsHeaders(preflight.headers),
    warnings,
    errors,
  };
}
```

## Common misconfigurations

### 1. Wildcard with credentials

```
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
```

Browsers will reject this combination. The preflight may return 204, but the browser's CORS check will fail before the JavaScript can read the response.

Fix:
```
Access-Control-Allow-Origin: https://your-specific-app.com
Access-Control-Allow-Credentials: true
```

### 2. Missing Vary: Origin

A server that dynamically reflects the requesting origin must include `Origin` in `Vary`:

```
# Wrong
Access-Control-Allow-Origin: https://app-a.example.com
Vary: Accept-Encoding

# Correct
Access-Control-Allow-Origin: https://app-a.example.com
Vary: Accept-Encoding, Origin
```

Without `Vary: Origin`, a CDN or proxy may cache the first response and return `Access-Control-Allow-Origin: https://app-a.example.com` to a request from `https://app-b.example.com`, causing that request to fail, or serve the response to an unauthorized origin if the cache does not validate.

### 3. Missing Access-Control-Allow-Headers

If the server does not list a requested header in `Access-Control-Allow-Headers`, the browser will block the request even if `Access-Control-Allow-Origin` matches.

```
# Request
Access-Control-Request-Headers: Authorization, X-Custom-Header

# Response missing X-Custom-Header
Access-Control-Allow-Headers: Content-Type, Authorization
# X-Custom-Header is missing - browser will block
```

### 4. Inconsistent headers between preflight and actual

Some frameworks return CORS headers on the preflight response but not on the actual response. Browsers require `Access-Control-Allow-Origin` on both.

cors-tester checks CORS headers on the actual response in addition to the preflight response.

## Header extraction helper

```typescript
const CORS_HEADER_NAMES = [
  'access-control-allow-origin',
  'access-control-allow-methods',
  'access-control-allow-headers',
  'access-control-allow-credentials',
  'access-control-max-age',
  'access-control-expose-headers',
  'vary',
] as const;

function extractCorsHeaders(
  headers: Record<string, string>
): Record<string, string> {
  return Object.fromEntries(
    CORS_HEADER_NAMES
      .filter(name => name in headers)
      .map(name => [name, headers[name]])
  );
}
```

Related Skills

SKILL.md - cors-tester

7
from heldernoid/agentic-build-templates

## What this tool does

Skill: Uptime Monitoring

7
from heldernoid/agentic-build-templates

## Overview

Skill: Status Page

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.

websocket-realtime

7
from heldernoid/agentic-build-templates

Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".

poll-builder

7
from heldernoid/agentic-build-templates

Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.

Skill: personal-finance

7
from heldernoid/agentic-build-templates

## Overview

Skill: csv-import

7
from heldernoid/agentic-build-templates

## Overview

Skill: Syntax Highlighting

7
from heldernoid/agentic-build-templates

## Purpose